Add inventory management in character sheet.
This commit is contained in:
parent
73b0fdf3f5
commit
b9970ccdf8
BIN
db.sqlite-shm
BIN
db.sqlite-shm
Binary file not shown.
File diff suppressed because one or more lines are too long
|
|
@ -1,15 +1,15 @@
|
||||||
import type { Ability, Alignment, Character, CharacterConfig, CharacterVariables, CompiledCharacter, DamageType, FeatureItem, Level, MainStat, Resistance, SpellConfig, SpellElement, SpellType, TrainingLevel, WeaponType } from "~/types/character";
|
import type { Ability, Alignment, ArmorConfig, Character, CharacterConfig, CharacterVariables, CompiledCharacter, DamageType, FeatureItem, ItemConfig, ItemState, Level, MainStat, Resistance, SpellConfig, SpellElement, SpellType, TrainingLevel, WeaponConfig, WeaponType } from "~/types/character";
|
||||||
import { z } from "zod/v4";
|
import { z } from "zod/v4";
|
||||||
import characterConfig from '#shared/character-config.json';
|
import characterConfig from '#shared/character-config.json';
|
||||||
import proses, { preview } from "#shared/proses";
|
import proses, { preview } from "#shared/proses";
|
||||||
import { button, buttongroup, floater, foldable, input, loading, numberpicker, select, tabgroup, Toaster, toggle } from "#shared/components.util";
|
import { button, buttongroup, checkbox, floater, foldable, input, loading, multiselect, numberpicker, select, tabgroup, Toaster, toggle } from "#shared/components.util";
|
||||||
import { div, dom, icon, span, text } from "#shared/dom.util";
|
import { div, dom, icon, span, text } from "#shared/dom.util";
|
||||||
import { followermenu, fullblocker, tooltip } from "#shared/floating.util";
|
import { followermenu, fullblocker, tooltip } from "#shared/floating.util";
|
||||||
import { clamp } from "#shared/general.util";
|
import { clamp } from "#shared/general.util";
|
||||||
import markdown from "#shared/markdown.util";
|
import markdown from "#shared/markdown.util";
|
||||||
import { getText } from "./i18n";
|
import { getText } from "#shared/i18n";
|
||||||
import type { User } from "~/types/auth";
|
import type { User } from "~/types/auth";
|
||||||
import { MarkdownEditor } from "./editor.util";
|
import { MarkdownEditor } from "#shared/editor.util";
|
||||||
|
|
||||||
const config = characterConfig as CharacterConfig;
|
const config = characterConfig as CharacterConfig;
|
||||||
|
|
||||||
|
|
@ -134,7 +134,6 @@ const defaultCompiledCharacter: (character: Character) => CompiledCharacter = (c
|
||||||
aspect: "",
|
aspect: "",
|
||||||
notes: Object.assign({ public: '', private: '' }, character.notes),
|
notes: Object.assign({ public: '', private: '' }, character.notes),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const mainStatTexts: Record<MainStat, string> = {
|
export const mainStatTexts: Record<MainStat, string> = {
|
||||||
"strength": "Force",
|
"strength": "Force",
|
||||||
"dexterity": "Dextérité",
|
"dexterity": "Dextérité",
|
||||||
|
|
@ -153,7 +152,6 @@ export const mainStatShortTexts: Record<MainStat, string> = {
|
||||||
"charisma": "CHA",
|
"charisma": "CHA",
|
||||||
"psyche": "PSY",
|
"psyche": "PSY",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const elementTexts: Record<SpellElement, { class: string, text: string }> = {
|
export const elementTexts: Record<SpellElement, { class: string, text: string }> = {
|
||||||
fire: { class: 'text-light-red dark:text-dark-red border-light-red dark:border-dark-red bg-light-red dark:bg-dark-red', text: 'Feu' },
|
fire: { class: 'text-light-red dark:text-dark-red border-light-red dark:border-dark-red bg-light-red dark:bg-dark-red', text: 'Feu' },
|
||||||
ice: { class: 'text-light-blue dark:text-dark-blue border-light-blue dark:border-dark-blue bg-light-blue dark:bg-dark-blue', text: 'Glace' },
|
ice: { class: 'text-light-blue dark:text-dark-blue border-light-blue dark:border-dark-blue bg-light-blue dark:bg-dark-blue', text: 'Glace' },
|
||||||
|
|
@ -169,7 +167,6 @@ 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-2 py-px`, elementTexts[element].class],
|
||||||
text: elementTexts[element].text
|
text: elementTexts[element].text
|
||||||
});
|
});
|
||||||
|
|
||||||
export const alignmentTexts: Record<Alignment, string> = {
|
export const alignmentTexts: Record<Alignment, string> = {
|
||||||
'loyal_good': 'Loyal bon',
|
'loyal_good': 'Loyal bon',
|
||||||
'neutral_good': 'Neutre bon',
|
'neutral_good': 'Neutre bon',
|
||||||
|
|
@ -182,7 +179,6 @@ export const alignmentTexts: Record<Alignment, string> = {
|
||||||
'chaotic_evil': 'Chaotique mauvais',
|
'chaotic_evil': 'Chaotique mauvais',
|
||||||
};
|
};
|
||||||
export const spellTypeTexts: Record<SpellType, string> = { "instinct": "Instinct", "knowledge": "Savoir", "precision": "Précision", "arts": "Oeuvres" };
|
export const spellTypeTexts: Record<SpellType, string> = { "instinct": "Instinct", "knowledge": "Savoir", "precision": "Précision", "arts": "Oeuvres" };
|
||||||
|
|
||||||
export const abilityTexts: Record<Ability, string> = {
|
export const abilityTexts: Record<Ability, string> = {
|
||||||
"athletics": "Athlétisme",
|
"athletics": "Athlétisme",
|
||||||
"acrobatics": "Acrobatique",
|
"acrobatics": "Acrobatique",
|
||||||
|
|
@ -202,7 +198,6 @@ export const abilityTexts: Record<Ability, string> = {
|
||||||
"animalhandling": "Dressage",
|
"animalhandling": "Dressage",
|
||||||
"deception": "Mensonge"
|
"deception": "Mensonge"
|
||||||
};
|
};
|
||||||
|
|
||||||
export const resistanceTexts: Record<Resistance, string> = {
|
export const resistanceTexts: Record<Resistance, string> = {
|
||||||
'stun': 'Hébètement',
|
'stun': 'Hébètement',
|
||||||
'bleed': 'Saignement',
|
'bleed': 'Saignement',
|
||||||
|
|
@ -224,23 +219,19 @@ export const damageTypeTexts: Record<DamageType, string> = {
|
||||||
'slashing': 'Tranchant',
|
'slashing': 'Tranchant',
|
||||||
'thunder': 'Foudre',
|
'thunder': 'Foudre',
|
||||||
};
|
};
|
||||||
export const weaponTypeTexts: Record<WeaponType, string> = {
|
|
||||||
"light": "Arme légère",
|
|
||||||
"shield": "Bouclier",
|
|
||||||
"heavy": "Arme lourde",
|
|
||||||
"classic": "Arme",
|
|
||||||
"throw": "Arme de jet",
|
|
||||||
"natural": "Arme naturelle",
|
|
||||||
"twohanded": "Deux mains",
|
|
||||||
"finesse": "Arme maniable",
|
|
||||||
"reach": "Arme longue",
|
|
||||||
"projectile": "Arme à projectile",
|
|
||||||
};
|
|
||||||
|
|
||||||
export const CharacterNotesValidation = z.object({
|
export const CharacterNotesValidation = z.object({
|
||||||
public: z.string().optional(),
|
public: z.string().optional(),
|
||||||
private: z.string().optional(),
|
private: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
export const ItemStateValidation = z.object({
|
||||||
|
id: z.string(),
|
||||||
|
amount: z.number().min(1),
|
||||||
|
enchantments: z.array(z.string()).optional(),
|
||||||
|
charges: z.number().optional(),
|
||||||
|
equipped: z.boolean().optional(),
|
||||||
|
state: z.any().optional(),
|
||||||
|
})
|
||||||
export const CharacterVariablesValidation = z.object({
|
export const CharacterVariablesValidation = z.object({
|
||||||
health: z.number(),
|
health: z.number(),
|
||||||
mana: z.number(),
|
mana: z.number(),
|
||||||
|
|
@ -255,7 +246,9 @@ export const CharacterVariablesValidation = z.object({
|
||||||
state: z.number().min(1).max(7).or(z.literal(true)),
|
state: z.number().min(1).max(7).or(z.literal(true)),
|
||||||
})),
|
})),
|
||||||
spells: z.array(z.string()),
|
spells: z.array(z.string()),
|
||||||
items: z.array(z.string()),
|
items: z.array(ItemStateValidation),
|
||||||
|
|
||||||
|
money: z.number(),
|
||||||
});
|
});
|
||||||
export const CharacterValidation = z.object({
|
export const CharacterValidation = z.object({
|
||||||
id: z.number(),
|
id: z.number(),
|
||||||
|
|
@ -1229,6 +1222,65 @@ class AspectPicker extends BuilderTab
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Category = ItemConfig['category'];
|
||||||
|
type Rarity = ItemConfig['rarity'];
|
||||||
|
export const colorByRarity: Record<Rarity, string> = {
|
||||||
|
'common': 'text-light-100 dark:text-dark-100',
|
||||||
|
'uncommon': 'text-light-cyan dark:text-dark-cyan',
|
||||||
|
'rare': 'text-light-purple dark:text-dark-purple',
|
||||||
|
'legendary': 'text-light-orange dark:text-dark-orange'
|
||||||
|
}
|
||||||
|
export const weaponTypeTexts: Record<WeaponType, string> = {
|
||||||
|
"light": 'légère',
|
||||||
|
"shield": 'bouclier',
|
||||||
|
"heavy": 'lourde',
|
||||||
|
"classic": 'arme',
|
||||||
|
"throw": 'de jet',
|
||||||
|
"natural": 'naturelle',
|
||||||
|
"twohanded": 'à deux mains',
|
||||||
|
"finesse": 'maniable',
|
||||||
|
"reach": 'longue',
|
||||||
|
"projectile": 'à projectile',
|
||||||
|
}
|
||||||
|
export const armorTypeTexts: Record<ArmorConfig["type"], string> = {
|
||||||
|
'heavy': 'Armure lourde',
|
||||||
|
'light': 'Armure légère',
|
||||||
|
'medium': 'Armure',
|
||||||
|
}
|
||||||
|
export const categoryText: Record<Category, string> = {
|
||||||
|
'mundane': 'Objet',
|
||||||
|
'armor': 'Armure',
|
||||||
|
'weapon': 'Arme',
|
||||||
|
'wondrous': 'Objet magique'
|
||||||
|
};
|
||||||
|
export const rarityText: Record<Rarity, string> = {
|
||||||
|
'common': 'Commun',
|
||||||
|
'uncommon': 'Atypique',
|
||||||
|
'rare': 'Rare',
|
||||||
|
'legendary': 'Légendaire'
|
||||||
|
};
|
||||||
|
const subnameFactory = (item: ItemConfig, state?: ItemState): string[] => {
|
||||||
|
let result = [];
|
||||||
|
switch(item.category)
|
||||||
|
{
|
||||||
|
case 'armor':
|
||||||
|
result = [armorTypeTexts[(item as ArmorConfig).type]];
|
||||||
|
break;
|
||||||
|
case 'weapon':
|
||||||
|
result = ['Arme', ...(item as WeaponConfig).type.filter(e => e !== 'classic').map(e => weaponTypeTexts[e])];
|
||||||
|
break;
|
||||||
|
case 'mundane':
|
||||||
|
result = ['Objet'];
|
||||||
|
break;
|
||||||
|
case 'wondrous':
|
||||||
|
result = ['Objet magique'];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(state && state.enchantments !== undefined && state.enchantments.length > 0) result.push('Enchanté');
|
||||||
|
if(item.consummable) result.push('Consommable');
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
export class CharacterSheet
|
export class CharacterSheet
|
||||||
{
|
{
|
||||||
user: ComputedRef<User | null>;
|
user: ComputedRef<User | null>;
|
||||||
|
|
@ -1290,9 +1342,7 @@ export class CharacterSheet
|
||||||
|
|
||||||
{ id: 'spells', title: [ text('Sorts') ], content: () => this.spellTab(character) },
|
{ id: 'spells', title: [ text('Sorts') ], content: () => this.spellTab(character) },
|
||||||
|
|
||||||
{ id: 'inventory', title: [ text('Inventaire') ], content: () => [
|
{ id: 'inventory', title: [ text('Inventaire') ], content: () => this.itemsTab(character) },
|
||||||
|
|
||||||
] },
|
|
||||||
|
|
||||||
{ id: 'notes', title: [ text('Notes') ], content: () => [
|
{ id: 'notes', title: [ text('Notes') ], content: () => [
|
||||||
div('flex flex-col gap-2', [
|
div('flex flex-col gap-2', [
|
||||||
|
|
@ -1488,7 +1538,7 @@ export class CharacterSheet
|
||||||
div('flex flex-col gap-8', [
|
div('flex flex-col gap-8', [
|
||||||
div('flex flex-col gap-2', [
|
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-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-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: 16, height: 16, 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-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-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' }) ]),
|
||||||
]),
|
]),
|
||||||
|
|
@ -1503,7 +1553,7 @@ export class CharacterSheet
|
||||||
]),
|
]),
|
||||||
div('flex flex-col gap-2', [
|
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-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-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: 16, height: 16, 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-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-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' }) ]),
|
||||||
]),
|
]),
|
||||||
|
|
@ -1518,7 +1568,7 @@ export class CharacterSheet
|
||||||
]),
|
]),
|
||||||
div('flex flex-col gap-2', [
|
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-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-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: 16, height: 16, 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-1 border-t border-dashed border-light-50 dark:border-dark-50"),
|
||||||
]),
|
]),
|
||||||
|
|
||||||
|
|
@ -1576,14 +1626,14 @@ export class CharacterSheet
|
||||||
]),
|
]),
|
||||||
div('flex flex-row gap-2 items-center', [
|
div('flex flex-row gap-2 items-center', [
|
||||||
dom('span', { class: ['italic text-sm', { 'text-light-red dark:text-dark-red': character.variables.spells.length !== character.spellslots }], text: `${character.variables.spells.length}/${character.spellslots} sort${character.variables.spells.length > 1 ? 's' : ''} maitrisé${character.variables.spells.length > 1 ? 's' : ''}` }),
|
dom('span', { class: ['italic text-sm', { 'text-light-red dark:text-dark-red': character.variables.spells.length !== character.spellslots }], text: `${character.variables.spells.length}/${character.spellslots} sort${character.variables.spells.length > 1 ? 's' : ''} maitrisé${character.variables.spells.length > 1 ? 's' : ''}` }),
|
||||||
button(text('Modifier'), () => this.spellPanel(character, spells), 'py-1 px-4'),
|
button(text('Modifier'), () => this.spellPanel(character), 'py-1 px-4'),
|
||||||
])
|
])
|
||||||
]),
|
]),
|
||||||
div('flex flex-col gap-2', spells.map(e => e.dom))
|
div('flex flex-col gap-2', spells.map(e => e.dom))
|
||||||
])
|
])
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
spellPanel(character: CompiledCharacter, spelllist: Array<{ id: string, spell?: SpellConfig, source: string }>)
|
spellPanel(character: CompiledCharacter)
|
||||||
{
|
{
|
||||||
const availableSpells = Object.values(config.spells).filter(spell => {
|
const availableSpells = Object.values(config.spells).filter(spell => {
|
||||||
if (spell.rank === 4) return false;
|
if (spell.rank === 4) return false;
|
||||||
|
|
@ -1650,4 +1700,113 @@ export class CharacterSheet
|
||||||
const blocker = fullblocker([ container ], { closeWhenOutside: true, onClose: () => this.character?.saveVariables() });
|
const blocker = fullblocker([ container ], { closeWhenOutside: true, onClose: () => this.character?.saveVariables() });
|
||||||
setTimeout(() => container.setAttribute('data-state', 'active'), 1);
|
setTimeout(() => container.setAttribute('data-state', 'active'), 1);
|
||||||
}
|
}
|
||||||
|
itemsTab(character: CompiledCharacter)
|
||||||
|
{
|
||||||
|
let debounceId: NodeJS.Timeout | undefined;
|
||||||
|
//TODO: Recompile values on "equip" checkbox change
|
||||||
|
const items = (character.variables.items.map(e => ({ ...e, item: config.items[e.id] })).filter(e => !!e.item) as Array<ItemState & { item: ItemConfig }>).map(e => div('flex flex-row justify-between', [
|
||||||
|
div('flex flex-row items-center gap-4', [
|
||||||
|
div('flex flex-col gap-1', [ e.item.equippable ? checkbox({ defaultValue: e.equipped, change: v => {
|
||||||
|
e.equipped = v;
|
||||||
|
|
||||||
|
this.character!.variable('items', this.character!.character.variables.items);
|
||||||
|
|
||||||
|
debounceId && clearTimeout(debounceId);
|
||||||
|
debounceId = setTimeout(() => this.character?.saveVariables(), 2000);
|
||||||
|
|
||||||
|
this.tabs?.refresh();
|
||||||
|
}, class: { container: '!w-5 !h-5' } }) : checkbox({ disabled: true, class: { container: '!w-5 !h-5' } }), button(icon('radix-icons:trash', { width: 16, height: 17 }), () => {
|
||||||
|
const idx = this.character!.character.variables.items.findIndex(_e => _e.id === e.id);
|
||||||
|
if(idx === -1) return;
|
||||||
|
|
||||||
|
this.character!.character.variables.items[idx]!.amount--;
|
||||||
|
if(this.character!.character.variables.items[idx]!.amount >= 0) this.character!.character.variables.items.splice(idx, 1);
|
||||||
|
|
||||||
|
this.character!.variable('items', this.character!.character.variables.items);
|
||||||
|
|
||||||
|
debounceId && clearTimeout(debounceId);
|
||||||
|
debounceId = setTimeout(() => this.character?.saveVariables(), 2000);
|
||||||
|
|
||||||
|
this.tabs?.refresh();
|
||||||
|
}, 'p-px') ]),
|
||||||
|
div('flex flex-col gap-1', [ span([colorByRarity[e.item.rarity], 'text-lg'], e.item.name), div('flex flex-row gap-4 text-light-60 dark:text-dark-60 text-sm italic', subnameFactory(e.item, e).map(text)) ]),
|
||||||
|
]),
|
||||||
|
div('grid grid-cols-2 row-gap-2 col-gap-8', [
|
||||||
|
div('flex flex-row w-16 gap-2 justify-between items-center', [ icon('game-icons:bolt-drop', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }), span('flex-1', (e.item.powercost || (e.enchantments && e.enchantments.length > 0)) && e.item.capacity ? `${(e.item?.powercost ?? 0) + (e.enchantments?.reduce((p, v) => (config.enchantments[v]?.power ?? 0) + p, 0) ?? 0)}/${e.item.capacity}` : '-') ]),
|
||||||
|
div('flex flex-row w-16 gap-2 justify-between items-center', [ icon('mdi:weight', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }), span('flex-1', e.item.weight?.toString() ?? '-') ]),
|
||||||
|
div('flex flex-row w-16 gap-2 justify-between items-center', [ icon('game-icons:battery-pack', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }), span('flex-1', e.charges && e.item.charge ? `${e.charges}/${e.item.charge}` : '-') ]),
|
||||||
|
div('flex flex-row w-16 gap-2 justify-between items-center', [ icon('radix-icons:cross-2', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }), span('flex-1', e.amount?.toString() ?? '-') ])
|
||||||
|
])
|
||||||
|
]));
|
||||||
|
|
||||||
|
const power = 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), 0);
|
||||||
|
const weight = character.variables.items.reduce((p, v) => p + (config.items[v.id]?.weight ?? 0), 0);
|
||||||
|
|
||||||
|
return [
|
||||||
|
div('flex flex-col gap-2', [
|
||||||
|
div('flex flex-row justify-end items-center gap-8', [
|
||||||
|
dom('span', { class: ['italic text-sm', { 'text-light-red dark:text-dark-red': weight > character.itempower }], text: `Poids total: ${weight}/${character.itempower}` }),
|
||||||
|
dom('span', { class: ['italic text-sm', { 'text-light-red dark:text-dark-red': power > (character.capacity === false ? 0 : character.capacity) }], text: `Puissance magique: ${power}/${character.capacity}` }),
|
||||||
|
button(text('Modifier'), () => this.itemsPanel(character), 'py-1 px-4'),
|
||||||
|
]),
|
||||||
|
div('grid grid-cols-2 flex-1 gap-4', items)
|
||||||
|
])
|
||||||
|
]
|
||||||
|
}
|
||||||
|
itemsPanel(character: CompiledCharacter)
|
||||||
|
{
|
||||||
|
const items = Object.values(config.items).map(item => ({ item, dom: foldable(() => [ markdown(getText(item.description)) ], [div('flex flex-row justify-between', [
|
||||||
|
div('flex flex-row items-center gap-4', [
|
||||||
|
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))) ]),
|
||||||
|
]),
|
||||||
|
div('flex flex-row items-center divide-x divide-light-50 dark:divide-dark-50 divide-dashed px-2', [
|
||||||
|
div('flex flex-row w-16 gap-2 justify-between items-center px-2', [ icon('game-icons:bolt-drop', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }), span('', item.powercost || item.capacity ? `${item.powercost ?? 0}/${item.capacity ?? 0}` : '-') ]),
|
||||||
|
div('flex flex-row w-16 gap-2 justify-between items-center px-2', [ icon('mdi:weight', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }), span('', item.weight?.toString() ?? '-') ]),
|
||||||
|
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];
|
||||||
|
if(item.equippable) list.push({ id: item.id, amount: 1, charges: item.charge, enchantments: [], equipped: false });
|
||||||
|
else if(list.find(e => e.id === item.id)) this.character!.character.variables.items.find(e => e.id === item.id)!.amount++;
|
||||||
|
else list.push({ id: item.id, amount: 1, charges: item.charge, enchantments: [] });
|
||||||
|
this.character!.variable('items', list); //TO REWORK
|
||||||
|
this.tabs?.refresh();
|
||||||
|
}, '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' } }) }));
|
||||||
|
|
||||||
|
const filters: { category: Category[], rarity: Rarity[], name: string, power: { min: number, max: number } } = {
|
||||||
|
category: [],
|
||||||
|
rarity: [],
|
||||||
|
name: '',
|
||||||
|
power: { min: 0, max: Infinity },
|
||||||
|
};
|
||||||
|
const applyFilters = () => {
|
||||||
|
content.replaceChildren(...items.filter(e =>
|
||||||
|
(filters.category.length === 0 || filters.category.includes(e.item.category)) &&
|
||||||
|
(filters.rarity.length === 0 || filters.rarity.includes(e.item.rarity)) &&
|
||||||
|
(filters.name === '' || e.item.name.toLowerCase().includes(filters.name.toLowerCase()))
|
||||||
|
).map(e => e.dom));
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = div('grid grid-cols-1 -my-2 overflow-y-auto gap-1');
|
||||||
|
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: "Gestion de l'inventaire" }),
|
||||||
|
div('flex flex-row gap-4 items-center', [ 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-row items-center gap-4', [
|
||||||
|
div('flex flex-row gap-2 items-center', [ text('Catégorie'), multiselect(Object.keys(categoryText).map(e => ({ text: categoryText[e as Category], value: e as Category })), { defaultValue: filters.category, change: v => { filters.category = v; applyFilters(); }, class: { container: 'w-40' } }) ]),
|
||||||
|
div('flex flex-row gap-2 items-center', [ text('Rareté'), multiselect(Object.keys(rarityText).map(e => ({ text: rarityText[e as Rarity], value: e as Rarity })), { defaultValue: filters.rarity, change: v => { filters.rarity = v; applyFilters(); }, class: { container: 'w-40' } }) ]),
|
||||||
|
div('flex flex-row gap-2 items-center', [ text('Nom'), input('text', { defaultValue: filters.name, input: v => { filters.name = v; applyFilters(); }, class: 'w-64' }) ]),
|
||||||
|
]),
|
||||||
|
content,
|
||||||
|
]);
|
||||||
|
applyFilters();
|
||||||
|
const blocker = fullblocker([ container ], { closeWhenOutside: true, onClose: () => this.character?.saveVariables() });
|
||||||
|
setTimeout(() => container.setAttribute('data-state', 'active'), 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -481,7 +481,7 @@ export function checkbox(settings?: { defaultValue?: boolean, change?: (this: HT
|
||||||
let state = settings?.defaultValue ?? false;
|
let state = settings?.defaultValue ?? false;
|
||||||
const element = dom("div", { class: [`group w-6 h-6 box-content flex items-center justify-center border border-light-50 dark:border-dark-50 bg-light-20 dark:bg-dark-20
|
const element = dom("div", { class: [`group w-6 h-6 box-content flex items-center justify-center border border-light-50 dark:border-dark-50 bg-light-20 dark:bg-dark-20
|
||||||
cursor-pointer hover:bg-light-30 dark:hover:bg-dark-30 hover:border-light-60 dark:hover:border-dark-60
|
cursor-pointer hover:bg-light-30 dark:hover:bg-dark-30 hover:border-light-60 dark:hover:border-dark-60
|
||||||
data-[disabled]:cursor-default data-[disabled]:border-dashed data-[disabled]:border-light-40 dark:data-[disabled]:border-dark-40 data-[disabled]:bg-0 dark:data-[disabled]:bg-0`, settings?.class?.container], attributes: { "data-state": state ? "checked" : "unchecked", "data-disabled": settings?.disabled }, listeners: {
|
data-[disabled]:cursor-default data-[disabled]:border-dashed data-[disabled]:border-light-40 dark:data-[disabled]:border-dark-40 data-[disabled]:bg-0 dark:data-[disabled]:bg-0 hover:data-[disabled]:bg-0 dark:hover:data-[disabled]:bg-0`, settings?.class?.container], attributes: { "data-state": state ? "checked" : "unchecked", "data-disabled": settings?.disabled }, listeners: {
|
||||||
click: function(e: Event) {
|
click: function(e: Event) {
|
||||||
if(this.hasAttribute('data-disabled'))
|
if(this.hasAttribute('data-disabled'))
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { MarkdownEditor } from "#shared/editor.util";
|
||||||
import { preview } from "#shared/proses";
|
import { preview } from "#shared/proses";
|
||||||
import { button, checkbox, combobox, foldable, input, multiselect, numberpicker, optionmenu, select, tabgroup, table, toggle, type Option } from "#shared/components.util";
|
import { button, checkbox, combobox, foldable, input, multiselect, numberpicker, optionmenu, select, tabgroup, table, toggle, type Option } from "#shared/components.util";
|
||||||
import { confirm, contextmenu, fullblocker, tooltip } from "#shared/floating.util";
|
import { confirm, contextmenu, fullblocker, tooltip } from "#shared/floating.util";
|
||||||
import { ABILITIES, abilityTexts, ALIGNMENTS, alignmentTexts, damageTypeTexts, elementTexts, LEVELS, MAIN_STATS, mainStatShortTexts, mainStatTexts, RESISTANCES, resistanceTexts, SPELL_ELEMENTS, SPELL_TYPES, spellTypeTexts, weaponTypeTexts } from "#shared/character.util";
|
import { ABILITIES, abilityTexts, ALIGNMENTS, alignmentTexts, categoryText, damageTypeTexts, elementTexts, LEVELS, MAIN_STATS, mainStatShortTexts, mainStatTexts, rarityText, RESISTANCES, resistanceTexts, SPELL_ELEMENTS, SPELL_TYPES, spellTypeTexts, weaponTypeTexts } from "#shared/character.util";
|
||||||
import characterConfig from "#shared/character-config.json";
|
import characterConfig from "#shared/character-config.json";
|
||||||
import { getID } from "#shared/general.util";
|
import { getID } from "#shared/general.util";
|
||||||
import markdown, { markdownReference, renderMDAsText } from "#shared/markdown.util";
|
import markdown, { markdownReference, renderMDAsText } from "#shared/markdown.util";
|
||||||
|
|
@ -13,18 +13,6 @@ import { getText } from "#shared/i18n";
|
||||||
|
|
||||||
type Category = ItemConfig['category'];
|
type Category = ItemConfig['category'];
|
||||||
type Rarity = ItemConfig['rarity'];
|
type Rarity = ItemConfig['rarity'];
|
||||||
const categoryText: Record<Category, string> = {
|
|
||||||
'mundane': 'Objet inerte',
|
|
||||||
'armor': 'Armure',
|
|
||||||
'weapon': 'Arme',
|
|
||||||
'wondrous': 'Objet magique'
|
|
||||||
};
|
|
||||||
const rarityText: Record<Rarity, string> = {
|
|
||||||
'common': 'Commun',
|
|
||||||
'uncommon': 'Peu commun',
|
|
||||||
'rare': 'Rare',
|
|
||||||
'legendary': 'Légendaire'
|
|
||||||
};
|
|
||||||
|
|
||||||
const config = characterConfig as CharacterConfig;
|
const config = characterConfig as CharacterConfig;
|
||||||
export class HomebrewBuilder
|
export class HomebrewBuilder
|
||||||
|
|
|
||||||
|
|
@ -52,11 +52,13 @@ export type CharacterVariables = {
|
||||||
poisons: Array<{ id: string, state: number | true }>;
|
poisons: Array<{ id: string, state: number | true }>;
|
||||||
spells: string[]; //Spell ID
|
spells: string[]; //Spell ID
|
||||||
items: ItemState[];
|
items: ItemState[];
|
||||||
|
|
||||||
|
money: number;
|
||||||
};
|
};
|
||||||
type ItemState = {
|
type ItemState = {
|
||||||
id: string;
|
id: string;
|
||||||
amount: number;
|
amount: number;
|
||||||
enchantments?: [];
|
enchantments?: string[];
|
||||||
charges?: number;
|
charges?: number;
|
||||||
equipped?: boolean;
|
equipped?: boolean;
|
||||||
state?: any;
|
state?: any;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue