Progress on tree features
This commit is contained in:
parent
f761e44569
commit
796b335b2e
|
|
@ -58,14 +58,16 @@ export type CharacterVariables = {
|
|||
money: number;
|
||||
};
|
||||
export type TreeStructure = {
|
||||
|
||||
name: string;
|
||||
nodes: FeatureID[];
|
||||
|
||||
// { 'from_id': { 'pathname': 'to_id' } };
|
||||
paths: Record<number, Record<string, number>>;
|
||||
};
|
||||
type CommonState = {
|
||||
capacity?: number;
|
||||
powercost?: number;
|
||||
};
|
||||
type StateBufferKeys = typeof ITEM_BUFFER_KEYS[number];
|
||||
type ArmorState = { loss: number, health?: number, absorb?: { flat?: number, percent?: number } };
|
||||
type WeaponState = { attack?: number | string, hit?: number };
|
||||
type WondrousState = { };
|
||||
|
|
@ -77,7 +79,6 @@ type ItemState = {
|
|||
charges?: number;
|
||||
equipped?: boolean;
|
||||
state?: (ArmorState | WeaponState | WondrousState | MundaneState) & CommonState;
|
||||
buffer?: Partial<Record<StateBufferKeys, PropertySum>>;
|
||||
};
|
||||
export type CharacterConfig = {
|
||||
peoples: Record<string, RaceConfig>;
|
||||
|
|
@ -198,7 +199,7 @@ export type FeatureEquipment = {
|
|||
id: FeatureID;
|
||||
category: "value";
|
||||
operation: "add" | "set" | "min";
|
||||
property: StateBufferKeys;
|
||||
property: `item/${RecursiveKeyOf<(ArmorState | WeaponState | WondrousState | MundaneState) & CommonState>}`;
|
||||
value: number | `modifier/${MainStat}` | false;
|
||||
}
|
||||
export type FeatureList = {
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -11,7 +11,7 @@ import { getText } from "#shared/i18n";
|
|||
import type { User } from "~/types/auth";
|
||||
import { MarkdownEditor } from "#shared/editor.util";
|
||||
import { Socket } from "#shared/websocket.util";
|
||||
import { raw, reactive } from '#shared/reactive';
|
||||
import { raw, reactive, reactivity } from '#shared/reactive';
|
||||
|
||||
const config = characterConfig as CharacterConfig;
|
||||
|
||||
|
|
@ -26,7 +26,6 @@ export const ALIGNMENTS = ['loyal_good', 'neutral_good', 'chaotic_good', 'loyal_
|
|||
export const RESISTANCES = ['stun','bleed','poison','fear','influence','charm','possesion','precision','knowledge','instinct'] as const;
|
||||
export const DAMAGE_TYPES = ['slashing', 'piercing', 'bludgening', 'magic', 'fire', 'thunder', 'cold'] as const;
|
||||
export const WEAPON_TYPES = ["light", "shield", "heavy", "classic", "throw", "natural", "twohanded", "finesse", "reach", "projectile"] as const;
|
||||
export const ITEM_BUFFER_KEYS = ['attack', 'hit', 'health', 'absorb/flat', 'absorb/percent'] as const;
|
||||
|
||||
export const defaultCharacter: Character = {
|
||||
id: -1,
|
||||
|
|
@ -184,7 +183,7 @@ export const elementTexts: Record<SpellElement, { class: string, text: string }>
|
|||
psyche: { class: 'text-light-purple dark:text-dark-purple border-light-purple dark:border-dark-purple bg-light-purple dark:bg-dark-purple', text: 'Psy' },
|
||||
};
|
||||
export const elementDom = (element: SpellElement) => dom("span", {
|
||||
class: [`border !border-opacity-50 rounded-full !bg-opacity-20 px-2 py-px`, elementTexts[element].class],
|
||||
class: [`border !border-opacity-50 rounded-full !bg-opacity-20 px-1 py-px text-sm font-semibold`, elementTexts[element].class],
|
||||
text: elementTexts[element].text
|
||||
});
|
||||
export const alignmentTexts: Record<Alignment, string> = {
|
||||
|
|
@ -336,8 +335,6 @@ export class CharacterCompiler
|
|||
});
|
||||
|
||||
Object.entries(value.abilities).forEach(e => this._result.abilities[e[0] as Ability] = e[1]);
|
||||
|
||||
value.variables.items.forEach((e) => this.update(e));
|
||||
}
|
||||
}
|
||||
get character(): Character
|
||||
|
|
@ -378,29 +375,14 @@ export class CharacterCompiler
|
|||
{
|
||||
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);
|
||||
}
|
||||
|
||||
update(item: ItemState)
|
||||
{
|
||||
item.buffer ??= {};
|
||||
|
||||
if(item.equipped)
|
||||
[...config.items[item.id]?.effects ?? [], ...item.enchantments?.flatMap(e => config.enchantments[e]?.effect) ?? []]?.forEach(e => this.apply(e, item));
|
||||
else
|
||||
[...config.items[item.id]?.effects ?? [], ...item.enchantments?.flatMap(e => config.enchantments[e]?.effect) ?? []]?.forEach(e => this.undo(e, item));
|
||||
|
||||
this.compile(Object.keys(item.buffer), item.buffer, item.state);
|
||||
this.compile(Object.keys(this._buffer));
|
||||
this.saveVariables();
|
||||
}
|
||||
saveVariables()
|
||||
{
|
||||
const variables = raw(this._character.variables);
|
||||
variables.items.forEach(e => delete e.buffer);
|
||||
clearTimeout(this._variableDebounce);
|
||||
this._variableDebounce = setTimeout(() => {
|
||||
useRequestFetch()(`/api/character/${this.character.id}/variables`, {
|
||||
method: 'POST',
|
||||
body: variables,
|
||||
body: raw(this._character.variables),
|
||||
}).then(() => {}).catch(() => {
|
||||
Toaster.add({ type: 'error', content: 'Impossible de mettre à jour les données', duration: 5000, timer: true });
|
||||
})
|
||||
|
|
@ -429,7 +411,7 @@ export class CharacterCompiler
|
|||
|
||||
config.features[feature]?.effect.forEach((effect) => this.undo(effect));
|
||||
}
|
||||
protected apply(feature?: FeatureItem | FeatureEquipment, item?: ItemState)
|
||||
protected apply(feature?: FeatureItem | FeatureEquipment)
|
||||
{
|
||||
if(!feature)
|
||||
return;
|
||||
|
|
@ -445,16 +427,15 @@ export class CharacterCompiler
|
|||
|
||||
return;
|
||||
case "value":
|
||||
const target = (item && ITEM_BUFFER_KEYS.includes(feature.property as any) ? item.buffer : this._buffer) as Record<string, PropertySum>;
|
||||
target[feature.property] ??= { list: [], value: 0, _dirty: true, min: -Infinity };
|
||||
this._buffer[feature.property] ??= { list: [], value: 0, _dirty: true, min: -Infinity };
|
||||
|
||||
target[feature.property]!.list.push({ operation: feature.operation, id: feature.id, value: feature.value });
|
||||
this._buffer[feature.property]!.list.push({ operation: feature.operation, id: feature.id, value: feature.value });
|
||||
|
||||
target[feature.property]!.min = -Infinity;
|
||||
target[feature.property]!._dirty = true;
|
||||
this._buffer[feature.property]!.min = -Infinity;
|
||||
this._buffer[feature.property]!._dirty = true;
|
||||
|
||||
if(feature.property.startsWith('modifier/'))
|
||||
Object.values(target).forEach(e => e._dirty = e.list.some(f => f.value === feature.property) ? true : e._dirty);
|
||||
Object.values(this._buffer).forEach(e => e._dirty = e.list.some(f => f.value === feature.property) ? true : e._dirty);
|
||||
|
||||
return;
|
||||
case "choice":
|
||||
|
|
@ -468,7 +449,7 @@ export class CharacterCompiler
|
|||
return;
|
||||
}
|
||||
}
|
||||
protected undo(feature?: FeatureItem | FeatureEquipment, item?: ItemState)
|
||||
protected undo(feature?: FeatureItem | FeatureEquipment)
|
||||
{
|
||||
if(!feature)
|
||||
return;
|
||||
|
|
@ -484,17 +465,16 @@ export class CharacterCompiler
|
|||
|
||||
return;
|
||||
case "value":
|
||||
const target = (item && ITEM_BUFFER_KEYS.includes(feature.property as any) ? item.buffer : this._buffer) as Record<string, PropertySum>;
|
||||
target[feature.property] ??= { list: [], value: 0, _dirty: true, min: -Infinity };
|
||||
this._buffer[feature.property] ??= { list: [], value: 0, _dirty: true, min: -Infinity };
|
||||
|
||||
const idx = target[feature.property]!.list.findIndex(e => e.id === feature.id);
|
||||
idx !== -1 && target[feature.property]!.list.splice(idx, 1);
|
||||
const idx = this._buffer[feature.property]!.list.findIndex(e => e.id === feature.id);
|
||||
idx !== -1 && this._buffer[feature.property]!.list.splice(idx, 1);
|
||||
|
||||
target[feature.property]!.min = -Infinity;
|
||||
target[feature.property]!._dirty = true;
|
||||
this._buffer[feature.property]!.min = -Infinity;
|
||||
this._buffer[feature.property]!._dirty = true;
|
||||
|
||||
if(feature.property.startsWith('modifier/'))
|
||||
Object.values(target).forEach(e => e._dirty = e.list.some(f => f.value === feature.property) ? true : e._dirty);
|
||||
Object.values(this._buffer).forEach(e => e._dirty = e.list.some(f => f.value === feature.property) ? true : e._dirty);
|
||||
|
||||
return;
|
||||
case "choice":
|
||||
|
|
@ -508,14 +488,14 @@ export class CharacterCompiler
|
|||
return;
|
||||
}
|
||||
}
|
||||
protected compile(queue: string[], _buffer: Record<string, PropertySum> = this._buffer, _target: any = this._result)
|
||||
protected compile(queue: string[])
|
||||
{
|
||||
for(let i = 0; i < queue.length; i++)
|
||||
{
|
||||
if(queue[i] === undefined || queue[i] === "") continue;
|
||||
|
||||
const property = queue[i]!;
|
||||
const buffer = _buffer[property];
|
||||
const buffer = this._buffer[property];
|
||||
|
||||
if(buffer && buffer._dirty === true)
|
||||
{
|
||||
|
|
@ -528,7 +508,7 @@ export class CharacterCompiler
|
|||
|
||||
if(typeof item.value === 'string') // Add or set a modifier
|
||||
{
|
||||
const modifier = _buffer[item.value as string]!;
|
||||
const modifier = this._buffer[item.value as string]!;
|
||||
if(modifier._dirty)
|
||||
{
|
||||
//Put it back in queue since its dependencies haven't been resolved yet
|
||||
|
|
@ -563,13 +543,13 @@ export class CharacterCompiler
|
|||
continue;
|
||||
|
||||
const path = property.split("/");
|
||||
const object = path.length === 1 ? _target : path.slice(0, -1).reduce((p, v) => { p[v] ??= {}; return p[v]; }, _target as any);
|
||||
const object = path.length === 1 ? this._result : path.slice(0, -1).reduce((p, v) => { p[v] ??= {}; return p[v]; }, this._result as any);
|
||||
|
||||
if(object.hasOwnProperty(path.slice(-1)[0]!))
|
||||
object[path.slice(-1)[0]!] = Math.max(sum, _buffer[property]!.min);
|
||||
object[path.slice(-1)[0]!] = Math.max(sum, this._buffer[property]!.min);
|
||||
|
||||
_buffer[property]!.value = Math.max(sum, _buffer[property]!.min);
|
||||
_buffer[property]!._dirty = false;
|
||||
this._buffer[property]!.value = Math.max(sum, this._buffer[property]!.min);
|
||||
this._buffer[property]!._dirty = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1615,31 +1595,31 @@ export class CharacterSheet
|
|||
]),
|
||||
|
||||
() => 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', label: 'Arme légère', class: 'text-sm text-light-70 dark:text-dark-70 max-w-20 truncate cursor-help decoration-dotted underline', }) : undefined,
|
||||
() => character.mastery.strength + character.mastery.dexterity > 0 ? proses('a', preview, [ text('Arme de jet') ], { href: 'regles/annexes/equipement#Les armes de jet', label: 'Arme de jet', class: 'text-sm text-light-70 dark:text-dark-70 max-w-20 truncate cursor-help decoration-dotted underline' }) : undefined,
|
||||
() => character.mastery.strength + character.mastery.dexterity > 0 ? proses('a', preview, [ text('Arme naturelle') ], { href: 'regles/annexes/equipement#Les armes naturelles', label: 'Arme naturelle', class: 'text-sm text-light-70 dark:text-dark-70 max-w-20 truncate cursor-help decoration-dotted underline' }) : undefined,
|
||||
() => character.mastery.strength > 1 ? proses('a', preview, [ text('Arme standard') ], { href: 'regles/annexes/equipement#Les armes', label: 'Arme standard', class: 'text-sm text-light-70 dark:text-dark-70 max-w-20 truncate cursor-help decoration-dotted underline' }) : undefined,
|
||||
() => character.mastery.strength > 1 ? proses('a', preview, [ text('Arme improvisée') ], { href: 'regles/annexes/equipement#Les armes improvisées', label: 'Arme improvisée', class: 'text-sm text-light-70 dark:text-dark-70 max-w-20 truncate cursor-help decoration-dotted underline' }) : undefined,
|
||||
() => character.mastery.strength > 2 ? proses('a', preview, [ text('Arme lourde') ], { href: 'regles/annexes/equipement#Les armes lourdes', label: 'Arme lourde', class: 'text-sm text-light-70 dark:text-dark-70 max-w-20 truncate cursor-help decoration-dotted underline' }) : undefined,
|
||||
() => character.mastery.strength > 3 ? proses('a', preview, [ text('Arme à deux mains') ], { href: 'regles/annexes/equipement#Les armes à deux mains', label: 'Arme à deux mains', class: 'text-sm text-light-70 dark:text-dark-70 max-w-20 truncate cursor-help decoration-dotted underline' }) : undefined,
|
||||
() => character.mastery.dexterity > 0 && character.mastery.strength > 1 ? proses('a', preview, [ text('Arme maniable') ], { href: 'regles/annexes/equipement#Les armes maniables', label: 'Arme maniable', class: 'text-sm text-light-70 dark:text-dark-70 max-w-20 truncate cursor-help decoration-dotted underline' }) : undefined,
|
||||
() => character.mastery.dexterity > 1 && character.mastery.strength > 1 ? proses('a', preview, [ text('Arme à projectiles') ], { href: 'regles/annexes/equipement#Les armes à projectiles', label: 'Arme à projectiles', class: 'text-sm text-light-70 dark:text-dark-70 max-w-20 truncate cursor-help decoration-dotted underline' }) : undefined,
|
||||
() => character.mastery.dexterity > 1 && character.mastery.strength > 2 ? proses('a', preview, [ text('Arme longue') ], { href: 'regles/annexes/equipement#Les armes longues', label: 'Arme longue', class: 'text-sm text-light-70 dark:text-dark-70 max-w-20 truncate cursor-help decoration-dotted underline' }) : undefined,
|
||||
() => character.mastery.shield > 0 ? proses('a', preview, [ text('Bouclier') ], { href: 'regles/annexes/equipement#Les boucliers', label: 'Bouclier', class: 'text-sm text-light-70 dark:text-dark-70 max-w-20 truncate cursor-help decoration-dotted underline' }) : undefined,
|
||||
() => character.mastery.shield > 0 && character.mastery.strength > 3 ? proses('a', preview, [ text('Bouclier à deux mains') ], { href: 'regles/annexes/equipement#Les boucliers à deux mains', label: 'Bouclier à deux mains', class: 'text-sm text-light-70 dark:text-dark-70 max-w-20 truncate cursor-help decoration-dotted underline' }) : undefined,
|
||||
() => character.mastery.strength + character.mastery.dexterity > 0 ? proses('a', preview, [ text('Arme légère') ], { href: 'regles/annexes/equipement#Les armes légères', label: 'Arme légère', class: 'text-sm text-light-70 dark:text-dark-70 cursor-help decoration-dotted underline', }) : undefined,
|
||||
() => character.mastery.strength + character.mastery.dexterity > 0 ? proses('a', preview, [ text('Arme de jet') ], { href: 'regles/annexes/equipement#Les armes de jet', label: 'Arme de jet', class: 'text-sm text-light-70 dark:text-dark-70 cursor-help decoration-dotted underline' }) : undefined,
|
||||
() => character.mastery.strength + character.mastery.dexterity > 0 ? proses('a', preview, [ text('Arme naturelle') ], { href: 'regles/annexes/equipement#Les armes naturelles', label: 'Arme naturelle', class: 'text-sm text-light-70 dark:text-dark-70 cursor-help decoration-dotted underline' }) : undefined,
|
||||
() => character.mastery.strength > 1 ? proses('a', preview, [ text('Arme standard') ], { href: 'regles/annexes/equipement#Les armes', label: 'Arme standard', class: 'text-sm text-light-70 dark:text-dark-70 cursor-help decoration-dotted underline' }) : undefined,
|
||||
() => character.mastery.strength > 1 ? proses('a', preview, [ text('Arme improvisée') ], { href: 'regles/annexes/equipement#Les armes improvisées', label: 'Arme improvisée', class: 'text-sm text-light-70 dark:text-dark-70 cursor-help decoration-dotted underline' }) : undefined,
|
||||
() => character.mastery.strength > 2 ? proses('a', preview, [ text('Arme lourde') ], { href: 'regles/annexes/equipement#Les armes lourdes', label: 'Arme lourde', class: 'text-sm text-light-70 dark:text-dark-70 cursor-help decoration-dotted underline' }) : undefined,
|
||||
() => character.mastery.strength > 3 ? proses('a', preview, [ text('Arme à deux mains') ], { href: 'regles/annexes/equipement#Les armes à deux mains', label: 'Arme à deux mains', class: 'text-sm text-light-70 dark:text-dark-70 cursor-help decoration-dotted underline' }) : undefined,
|
||||
() => character.mastery.dexterity > 0 && character.mastery.strength > 1 ? proses('a', preview, [ text('Arme maniable') ], { href: 'regles/annexes/equipement#Les armes maniables', label: 'Arme maniable', class: 'text-sm text-light-70 dark:text-dark-70 cursor-help decoration-dotted underline' }) : undefined,
|
||||
() => character.mastery.dexterity > 1 && character.mastery.strength > 1 ? proses('a', preview, [ text('Arme à projectiles') ], { href: 'regles/annexes/equipement#Les armes à projectiles', label: 'Arme à projectiles', class: 'text-sm text-light-70 dark:text-dark-70 cursor-help decoration-dotted underline' }) : undefined,
|
||||
() => character.mastery.dexterity > 1 && character.mastery.strength > 2 ? proses('a', preview, [ text('Arme longue') ], { href: 'regles/annexes/equipement#Les armes longues', label: 'Arme longue', class: 'text-sm text-light-70 dark:text-dark-70 cursor-help decoration-dotted underline' }) : undefined,
|
||||
() => character.mastery.shield > 0 ? proses('a', preview, [ text('Bouclier') ], { href: 'regles/annexes/equipement#Les boucliers', label: 'Bouclier', class: 'text-sm text-light-70 dark:text-dark-70 cursor-help decoration-dotted underline' }) : undefined,
|
||||
() => character.mastery.shield > 0 && character.mastery.strength > 3 ? proses('a', preview, [ text('Bouclier à deux mains') ], { href: 'regles/annexes/equipement#Les boucliers à deux mains', label: 'Bouclier à deux mains', class: 'text-sm text-light-70 dark:text-dark-70 cursor-help decoration-dotted underline' }) : 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', label: 'Armure légère', class: 'text-sm text-light-70 dark:text-dark-70 max-w-20 truncate cursor-help decoration-dotted underline' }) : undefined,
|
||||
() => character.mastery.armor > 1 ? proses('a', preview, [ text('Armure standard') ], { href: 'regles/annexes/equipement#Les armures', label: 'Armure standard', class: 'text-sm text-light-70 dark:text-dark-70 max-w-20 truncate cursor-help decoration-dotted underline' }) : undefined,
|
||||
() => character.mastery.armor > 2 ? proses('a', preview, [ text('Armure lourde') ], { href: 'regles/annexes/equipement#Les armures lourdes', label: 'Armure lourde', class: 'text-sm text-light-70 dark:text-dark-70 max-w-20 truncate cursor-help decoration-dotted underline' }) : undefined,
|
||||
() => character.mastery.armor > 0 ? proses('a', preview, [ text('Armure légère') ], { href: 'regles/annexes/equipement#Les armures légères', label: 'Armure légère', class: 'text-sm text-light-70 dark:text-dark-70 cursor-help decoration-dotted underline' }) : undefined,
|
||||
() => character.mastery.armor > 1 ? proses('a', preview, [ text('Armure standard') ], { href: 'regles/annexes/equipement#Les armures', label: 'Armure standard', class: 'text-sm text-light-70 dark:text-dark-70 cursor-help decoration-dotted underline' }) : undefined,
|
||||
() => character.mastery.armor > 2 ? proses('a', preview, [ text('Armure lourde') ], { href: 'regles/annexes/equipement#Les armures lourdes', label: 'Armure lourde', class: 'text-sm text-light-70 dark:text-dark-70 cursor-help decoration-dotted underline' }) : undefined,
|
||||
]) : undefined,
|
||||
|
||||
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 max-w-20 truncate 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 max-w-20 truncate 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 max-w-20 truncate 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 max-w-20 truncate cursor-help decoration-dotted underline' }), span('font-bold', text(() => character.spellranks.arts)) ]) : undefined,
|
||||
() => 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,
|
||||
])
|
||||
])
|
||||
]),
|
||||
|
|
@ -1904,7 +1884,7 @@ export class CharacterSheet
|
|||
const weight = div(() => ['flex flex-row min-w-16 gap-2 justify-between items-center px-2', { 'cursor-help': e.amount > 1 && !!item.weight }], [ icon('mdi:weight', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }), span(() => ({ 'underline decoration-1 decoration-dotted underline-offset-2': e.amount > 1 && !!item.weight }), () => item.weight ? `${item.weight * e.amount}` : '-') ]);
|
||||
return foldable(() => [
|
||||
markdown(getText(item.description)),
|
||||
() => e.enchantments && e.enchantments.length === 0 ? undefined : 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 border-light-35 dark:border-dark-35 bg-light-15 dark:bg-dark-15 px-2 rounded-full py-px bg-light-cyan dark:bg-dark-cyan bg-opacity-20 dark:bg-opacity-20', [ span('text-sm font-semibold tracking-thigh', 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 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 border-accent-blue px-2 rounded-full py-px bg-accent-blue bg-opacity-20', [ span('text-sm font-semibold tracking-thigh', 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'), () => {
|
||||
|
||||
|
|
@ -2059,10 +2039,11 @@ export class CharacterSheet
|
|||
]),
|
||||
div('grid grid-cols-1 -my-2 overflow-y-auto gap-1', { list: () => Object.values(config.enchantments).filter(e => restrict(e, current.item?.id)), render: (enchant, _c) => _c ?? foldable(() => [ markdown(getText(enchant.description)) ], [div('flex flex-row justify-between', [
|
||||
div('flex flex-row items-center gap-4', [ span('text-lg', enchant.name) ]),
|
||||
div('flex flex-row items-center divide-x divide-light-50 dark:divide-dark-50 divide-dashed px-2', [
|
||||
div('flex flex-row items-center divide-x divide-light-50 dark:divide-dark-50 divide-dashed px-2 gap-4', [
|
||||
span('italic text-sm', `Puissance magique: ${enchant.power}`),
|
||||
button(icon('radix-icons:plus', { width: 16, height: 16 }), () => {
|
||||
current.item!.enchantments?.push(enchant.id);
|
||||
// TODO: Object.assign(current.item!.state, current.item!.enchantments?.reduce((p, id) => { config.enchantments[id]?.effect.filter(e => e.category === "value" && e.property.startsWith('item/')); return p; }, {}));
|
||||
|
||||
this.character?.saveVariables();
|
||||
}, 'p-1 !border-solid !border-r'),
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import { reactive } from "./reactive";
|
|||
type Category = ItemConfig['category'];
|
||||
type Rarity = ItemConfig['rarity'];
|
||||
|
||||
const config = characterConfig as CharacterConfig;
|
||||
const config = reactive(characterConfig as CharacterConfig);
|
||||
export class HomebrewBuilder
|
||||
{
|
||||
private _container: RedrawableHTML;
|
||||
|
|
@ -32,6 +32,7 @@ export class HomebrewBuilder
|
|||
{ id: 'aspects', title: [ text("Aspects") ], content: () => this.aspects() },
|
||||
{ id: 'actions', title: [ text("Actions") ], content: () => this.actions() },
|
||||
{ id: 'items', title: [ text("Objets") ], content: () => this.items() },
|
||||
{ id: 'trees', title: [ text("Arbres") ], content: () => this.trees() },
|
||||
], { focused: 'training', class: { container: 'flex-1 outline-none max-w-full w-full overflow-y-auto', tabbar: 'flex w-full flex-row gap-4 items-center justify-center relative' } });
|
||||
|
||||
this._tabs.children[0]?.appendChild(tooltip(button(icon('radix-icons:clipboard'), () => this.save(), 'p-1'), 'Copier', 'bottom'));
|
||||
|
|
@ -459,6 +460,31 @@ export class HomebrewBuilder
|
|||
const optionHolder = div('grid grid-cols-3 gap-2', options.map(e => e.dom));
|
||||
return [ div('flex px-8 py-4 flex-col gap-4', [ div('flex flex-row-reverse', [ button(icon('radix-icons:plus'), optionmenu([{ title: 'Objet inerte', click: () => add('mundane') }, { title: 'Armure', click: () => add('armor') }, { title: 'Arme', click: () => add('weapon') }, { title: 'Objet magique', click: () => add('wondrous') }], { position: 'left-start' }), 'p-1') ]), optionHolder ] ) ];
|
||||
}
|
||||
trees()
|
||||
{
|
||||
const add = () => {
|
||||
const description = getID();
|
||||
setText(description, "");
|
||||
const id = getID();
|
||||
config.features[id] = {
|
||||
id,
|
||||
description,
|
||||
effect: [],
|
||||
};
|
||||
config.trees[editing.tree!]!.nodes.push(id);
|
||||
}
|
||||
const editing = reactive({ tree: undefined as string | undefined });
|
||||
return [div('', [
|
||||
() => editing.tree !== undefined ? undefined : div('flex flex-row gap-1 justify-start overflow-x-auto max-w-full', { list: Object.values(config.trees), render: (e, _c) => _c ?? div('grid grid-cols-2 gap-2 items-baseline w-64 border border-light-35 dark:border-dark-35 p-2', [ span('text-lg font-semibold tracking-thigh', e.name), div('flex flex-row justify-end', [ tooltip(button(icon('radix-icons:pencil-1', { width: 16, height: 16 }), () => editing.tree = e.name, 'p-1'), 'Modifier', 'left') ]), span('italic', `${Object.keys(e.nodes).length} nodes`) ]) }),
|
||||
() => editing.tree === undefined ? undefined : div('', [
|
||||
foldable([ div('flex flex-col gap-2', { list: config.trees[editing.tree]!.nodes, render: (e, _c) => _c ?? 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: () => {
|
||||
FeaturePanel.edit(config.features[e]!).then(feature => {
|
||||
config.features[e] = feature;
|
||||
}).catch(e => {});
|
||||
}}}, [ markdown(getText(config.features[e]!.description), undefined, { tags: { a: preview } }) ]) }) ], [ span('text-lg font-bold px-2', 'Nodes'), button(text('Nouvelle node'), () => add(), 'py-1 px-2') ], { open: true, class: { title: 'flex flex-row justify-between' } })
|
||||
]),
|
||||
])];
|
||||
}
|
||||
private save()
|
||||
{
|
||||
navigator.clipboard.writeText(JSON.stringify(config));
|
||||
|
|
|
|||
Loading…
Reference in New Issue