You've already forked obsidian-visualiser
Item Improvements added to the homebrew manager.
This commit is contained in:
@@ -33,7 +33,7 @@
|
|||||||
</NavigationMenuTrigger>
|
</NavigationMenuTrigger>
|
||||||
<NavigationMenuContent class="absolute top-0 w-full sm:w-auto bg-light-0 dark:bg-dark-0 border border-light-30 dark:border-dark-30 py-2 z-20 flex flex-col">
|
<NavigationMenuContent class="absolute top-0 w-full sm:w-auto bg-light-0 dark:bg-dark-0 border border-light-30 dark:border-dark-30 py-2 z-20 flex flex-col">
|
||||||
<NuxtLink :href="{ name: 'character-list' }" class="hover:bg-light-30 dark:hover:bg-dark-30 px-4 py-2 select-none" active-class="!text-accent-blue"><span class="flex-1 truncate">Personnages publics</span></NuxtLink>
|
<NuxtLink :href="{ name: 'character-list' }" class="hover:bg-light-30 dark:hover:bg-dark-30 px-4 py-2 select-none" active-class="!text-accent-blue"><span class="flex-1 truncate">Personnages publics</span></NuxtLink>
|
||||||
<NuxtLink :href="{ name: 'character-id-edit', params: { id: 'new' } }" class="hover:bg-light-30 dark:hover:bg-dark-30 px-4 py-2 select-none" active-class="!text-accent-blue"><span class="flex-1 truncate">Nouveau personnage</span></NuxtLink>
|
<NuxtLink :href="{ name: 'character-id', params: { id: 'new' } }" class="hover:bg-light-30 dark:hover:bg-dark-30 px-4 py-2 select-none" active-class="!text-accent-blue"><span class="flex-1 truncate">Nouveau personnage</span></NuxtLink>
|
||||||
</NavigationMenuContent>
|
</NavigationMenuContent>
|
||||||
</NavigationMenuItem>
|
</NavigationMenuItem>
|
||||||
</NavigationMenuList>
|
</NavigationMenuList>
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { CharacterBuilder } from '~~/shared/character';
|
|
||||||
import { unifySlug } from '~~/shared/general';
|
|
||||||
|
|
||||||
definePageMeta({
|
|
||||||
requiresAuth: true,
|
|
||||||
validState: true,
|
|
||||||
});
|
|
||||||
const id = unifySlug(useRouter().currentRoute.value.params.id ?? "new");
|
|
||||||
const container = useTemplateRef('container');
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
queueMicrotask(() => {
|
|
||||||
if(container.value)
|
|
||||||
{
|
|
||||||
const builder = new CharacterBuilder(container.value, id === 'new' ? undefined : id);
|
|
||||||
|
|
||||||
useShortcuts({
|
|
||||||
"Meta_S": () => builder.save(false),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="flex flex-1 max-w-full flex-col align-center" ref="container"></div>
|
|
||||||
</template>
|
|
||||||
@@ -55,7 +55,7 @@ async function duplicateCharacter(id: number)
|
|||||||
</div>
|
</div>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<div class="flex justify-around items-center py-2 px-4 gap-4">
|
<div class="flex justify-around items-center py-2 px-4 gap-4">
|
||||||
<NuxtLink :to="{ name: 'character-id-edit', params: { id: character.id } }" class="text-sm font-bold cursor-pointer hover:text-accent-blue">Editer</NuxtLink>
|
<NuxtLink :to="{ name: 'character-id', params: { id: character.id } }" class="text-sm font-bold cursor-pointer hover:text-accent-blue">Editer</NuxtLink>
|
||||||
<span class="w-px h-full bg-light-50 dark:bg-dark-50"></span>
|
<span class="w-px h-full bg-light-50 dark:bg-dark-50"></span>
|
||||||
<NuxtLink @click="duplicateCharacter(character.id)" class="text-sm font-bold cursor-pointer hover:text-accent-blue">Dupliquer</NuxtLink>
|
<NuxtLink @click="duplicateCharacter(character.id)" class="text-sm font-bold cursor-pointer hover:text-accent-blue">Dupliquer</NuxtLink>
|
||||||
<span class="w-px h-full bg-light-50 dark:bg-dark-50"></span>
|
<span class="w-px h-full bg-light-50 dark:bg-dark-50"></span>
|
||||||
@@ -83,7 +83,7 @@ async function duplicateCharacter(id: number)
|
|||||||
<NuxtLink v-if="user && user.state === 1" class="inline-flex justify-center items-center outline-none leading-none transition-[box-shadow]
|
<NuxtLink v-if="user && user.state === 1" class="inline-flex justify-center items-center outline-none leading-none transition-[box-shadow]
|
||||||
text-light-100 dark:text-dark-100 bg-light-20 dark:bg-dark-20 border border-light-40 dark:border-dark-40
|
text-light-100 dark:text-dark-100 bg-light-20 dark:bg-dark-20 border border-light-40 dark:border-dark-40
|
||||||
hover:bg-light-25 dark:hover:bg-dark-25 hover:border-light-50 dark:hover:border-dark-50
|
hover:bg-light-25 dark:hover:bg-dark-25 hover:border-light-50 dark:hover:border-dark-50
|
||||||
focus:bg-light-30 dark:focus:bg-dark-30 focus:border-light-50 dark:focus:border-dark-50 focus:shadow-raw focus:shadow-light-50 dark:focus:shadow-dark-50 py-2 px-4" :to="{ name: 'character-id-edit', params: { id: 'new' } }">Nouveau personnage</NuxtLink>
|
focus:bg-light-30 dark:focus:bg-dark-30 focus:border-light-50 dark:focus:border-dark-50 focus:shadow-raw focus:shadow-light-50 dark:focus:shadow-dark-50 py-2 px-4" :to="{ name: 'character-id', params: { id: 'new' } }">Nouveau personnage</NuxtLink>
|
||||||
<div v-else>Veuillez validez votre adresse mail pour pouvoir créer des personnages.</div>
|
<div v-else>Veuillez validez votre adresse mail pour pouvoir créer des personnages.</div>
|
||||||
<NuxtLink class="inline-flex justify-center items-center outline-none leading-none transition-[box-shadow]
|
<NuxtLink class="inline-flex justify-center items-center outline-none leading-none transition-[box-shadow]
|
||||||
text-light-100 dark:text-dark-100 bg-light-20 dark:bg-dark-20 border border-light-40 dark:border-dark-40
|
text-light-100 dark:text-dark-100 bg-light-20 dark:bg-dark-20 border border-light-40 dark:border-dark-40
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ const { user } = useUserSession();
|
|||||||
<NuxtLink class="inline-flex justify-center items-center outline-none leading-none transition-[box-shadow]
|
<NuxtLink class="inline-flex justify-center items-center outline-none leading-none transition-[box-shadow]
|
||||||
text-light-100 dark:text-dark-100 bg-light-20 dark:bg-dark-20 border border-light-40 dark:border-dark-40
|
text-light-100 dark:text-dark-100 bg-light-20 dark:bg-dark-20 border border-light-40 dark:border-dark-40
|
||||||
hover:bg-light-25 dark:hover:bg-dark-25 hover:border-light-50 dark:hover:border-dark-50
|
hover:bg-light-25 dark:hover:bg-dark-25 hover:border-light-50 dark:hover:border-dark-50
|
||||||
focus:bg-light-30 dark:focus:bg-dark-30 focus:border-light-50 dark:focus:border-dark-50 focus:shadow-raw focus:shadow-light-50 dark:focus:shadow-dark-50 py-2 px-4" :to="{ name: 'character-id-edit', params: { id: 'new' } }">Nouveau personnage</NuxtLink>
|
focus:bg-light-30 dark:focus:bg-dark-30 focus:border-light-50 dark:focus:border-dark-50 focus:shadow-raw focus:shadow-light-50 dark:focus:shadow-dark-50 py-2 px-4" :to="{ name: 'character-id', params: { id: 'new' } }">Nouveau personnage</NuxtLink>
|
||||||
</template>
|
</template>
|
||||||
<div v-else>Veuillez valider votre adresse mail pour pouvoir créer des personnages.</div>
|
<div v-else>Veuillez valider votre adresse mail pour pouvoir créer des personnages.</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
26
app/types/character.d.ts
vendored
26
app/types/character.d.ts
vendored
@@ -1,4 +1,4 @@
|
|||||||
import type { MAIN_STATS, ABILITIES, LEVELS, TRAINING_LEVELS, SPELL_TYPES, CATEGORIES, SPELL_ELEMENTS, ALIGNMENTS, RESISTANCES, DAMAGE_TYPES, WEAPON_TYPES, PropertySum, ITEM_BUFFER_KEYS } from "#shared/character";
|
import type { MAIN_STATS, ABILITIES, LEVELS, TRAINING_LEVELS, SPELL_TYPES, CATEGORIES, SPELL_ELEMENTS, ALIGNMENTS, RESISTANCES, DAMAGE_TYPES, WEAPON_TYPES, CRAFTING_TYPES, PropertySum, ITEM_BUFFER_KEYS } from "#shared/character";
|
||||||
import type { Localized } from "../types/general";
|
import type { Localized } from "../types/general";
|
||||||
|
|
||||||
export type MainStat = typeof MAIN_STATS[number];
|
export type MainStat = typeof MAIN_STATS[number];
|
||||||
@@ -12,6 +12,7 @@ export type Alignment = typeof ALIGNMENTS[number];
|
|||||||
export type Resistance = typeof RESISTANCES[number];
|
export type Resistance = typeof RESISTANCES[number];
|
||||||
export type DamageType = typeof DAMAGE_TYPES[number];
|
export type DamageType = typeof DAMAGE_TYPES[number];
|
||||||
export type WeaponType = typeof WEAPON_TYPES[number];
|
export type WeaponType = typeof WEAPON_TYPES[number];
|
||||||
|
export type CraftingType = typeof CRAFTING_TYPES[number];
|
||||||
|
|
||||||
export type FeatureID = string;
|
export type FeatureID = string;
|
||||||
export type FeatureEffectID = string;
|
export type FeatureEffectID = string;
|
||||||
@@ -56,8 +57,9 @@ export type CharacterVariables = {
|
|||||||
spells: string[]; //Spell ID
|
spells: string[]; //Spell ID
|
||||||
items: ItemState[];
|
items: ItemState[];
|
||||||
|
|
||||||
money: number;
|
components: { money: number, natural: number, mineral: number, processed: number, magical: number };
|
||||||
transformed: boolean;
|
transformed: boolean;
|
||||||
|
craft?: { item: string, progress: number };
|
||||||
};
|
};
|
||||||
export type TreeLeaf = {
|
export type TreeLeaf = {
|
||||||
id: FeatureID;
|
id: FeatureID;
|
||||||
@@ -81,7 +83,7 @@ type MundaneState = { };
|
|||||||
type ItemState = {
|
type ItemState = {
|
||||||
id: string;
|
id: string;
|
||||||
amount: number;
|
amount: number;
|
||||||
enchantments?: string[];
|
improvements?: string[];
|
||||||
charges?: number;
|
charges?: number;
|
||||||
equipped?: boolean;
|
equipped?: boolean;
|
||||||
state?: (ArmorState | WeaponState | WondrousState | MundaneState) & CommonState;
|
state?: (ArmorState | WeaponState | WondrousState | MundaneState) & CommonState;
|
||||||
@@ -93,7 +95,7 @@ export type CharacterConfig = {
|
|||||||
spells: Record<string, SpellConfig>;
|
spells: Record<string, SpellConfig>;
|
||||||
aspects: Record<string, AspectConfig>;
|
aspects: Record<string, AspectConfig>;
|
||||||
features: Record<FeatureID, Feature>;
|
features: Record<FeatureID, Feature>;
|
||||||
enchantments: Record<string, EnchantementConfig>;
|
improvements: Record<string, ImprovementConfig>;
|
||||||
items: Record<string, ItemConfig>;
|
items: Record<string, ItemConfig>;
|
||||||
action: Record<string, { id: string, name: string, description: string, cost?: number, variants?: string[], parent?: string }>;
|
action: Record<string, { id: string, name: string, description: string, cost?: number, variants?: string[], parent?: string }>;
|
||||||
reaction: Record<string, { id: string, name: string, description: string, cost?: number, variants?: string[], parent?: string }>;
|
reaction: Record<string, { id: string, name: string, description: string, cost?: number, variants?: string[], parent?: string }>;
|
||||||
@@ -107,13 +109,15 @@ export type CharacterConfig = {
|
|||||||
poison: Record<FeatureID, { name: string, difficulty: number, efficienty: number, solubility: number }>; //TODO
|
poison: Record<FeatureID, { name: string, difficulty: number, efficienty: number, solubility: number }>; //TODO
|
||||||
dedication: Record<FeatureID, { name: string, requirement: Array<{ stat: MainStat, amount: number }> }>; //TODO
|
dedication: Record<FeatureID, { name: string, requirement: Array<{ stat: MainStat, amount: number }> }>; //TODO
|
||||||
};
|
};
|
||||||
export type EnchantementConfig = {
|
export type ImprovementConfig = {
|
||||||
id: string;
|
id: string;
|
||||||
name: string; //TODO -> TextID
|
name: string; //TODO -> TextID
|
||||||
description: i18nID;
|
description: i18nID;
|
||||||
|
rarity: 'common' | 'uncommon' | 'rare' | 'veryrare' | 'legendary';
|
||||||
effect: Array<FeatureEquipment | FeatureValue | FeatureList>;
|
effect: Array<FeatureEquipment | FeatureValue | FeatureList>;
|
||||||
power: number;
|
power: number;
|
||||||
restrictions?: Array<'armor' | 'mundane' | 'wondrous' | 'weapon' | `armor/${ArmorConfig['type']}` | `weapon/${WeaponConfig['type'][number]}`>; // Need to respect *any* of the restriction, not every restrictions.
|
craft: { difficulty?: number, ability?: CraftingType };
|
||||||
|
restrictions?: Partial<Record<'armor' | 'mundane' | 'wondrous' | 'weapon' | `armor/${ArmorConfig['type']}` | `weapon/${WeaponConfig['type'][number]}` | string, boolean>>; // Need to respect *any* of the restriction, not every restrictions.
|
||||||
cursed: boolean;
|
cursed: boolean;
|
||||||
}
|
}
|
||||||
export type ItemConfig = CommonItemConfig & (ArmorConfig | WeaponConfig | WondrousConfig | MundaneConfig);
|
export type ItemConfig = CommonItemConfig & (ArmorConfig | WeaponConfig | WondrousConfig | MundaneConfig);
|
||||||
@@ -122,18 +126,18 @@ type CommonItemConfig = {
|
|||||||
name: string; //TODO -> TextID
|
name: string; //TODO -> TextID
|
||||||
flavoring?: i18nID;
|
flavoring?: i18nID;
|
||||||
description: i18nID;
|
description: i18nID;
|
||||||
rarity: 'common' | 'uncommon' | 'rare' | 'legendary';
|
rarity: 'common' | 'uncommon' | 'rare' | 'veryrare' | 'legendary';
|
||||||
weight?: number; //Optionnal but highly recommended
|
weight?: number; //Optionnal but highly recommended
|
||||||
price?: number; //Optionnal but highly recommended
|
price?: number; //Optionnal but highly recommended
|
||||||
capacity?: number; //Optionnal as most mundane items should not receive enchantments (potions, herbal heals, etc...)
|
capacity?: number; //Optionnal as most mundane items should not receive improvements (potions, herbal heals, etc...)
|
||||||
powercost?: number; //Optionnal
|
powercost?: number; //Optionnal
|
||||||
charge?: number //Max amount of charges
|
charge?: number //Max amount of charges
|
||||||
enchantments?: string[]; //Enchantment ID
|
improvements?: string[]; //Improvement ID
|
||||||
effects?: Array<FeatureValue | FeatureState | FeatureEquipment | FeatureList>;
|
effects?: Array<FeatureValue | FeatureState | FeatureEquipment | FeatureList>;
|
||||||
equippable: boolean;
|
equippable: boolean;
|
||||||
consummable: boolean;
|
consummable: boolean;
|
||||||
craft?: { mineral: number, natural: number, processed: number, magical: number };
|
craft: { mineral: number, natural: number, processed: number, magical: number, difficulty?: number, ability?: CraftingType };
|
||||||
//variants?: string[];
|
variants?: string[]; //ID array
|
||||||
};
|
};
|
||||||
type ArmorConfig = {
|
type ArmorConfig = {
|
||||||
category: 'armor';
|
category: 'armor';
|
||||||
|
|||||||
@@ -245,16 +245,16 @@ export class CampaignSheet
|
|||||||
|
|
||||||
const items = this.campaign.items;
|
const items = this.campaign.items;
|
||||||
|
|
||||||
const money = {
|
/* 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', () => this.campaign!.money.toLocaleString(undefined, { useGrouping: true })), icon('ph:coin', { width: 16, height: 16 }) ]),
|
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', () => this.campaign!.money.toLocaleString(undefined, { useGrouping: true })), icon('ph:coin', { width: 16, height: 16 }) ]),
|
||||||
edit: numberpicker({ defaultValue: this.campaign.money, change: v => { this.campaign!.money = v; this.saveVariables(); money.edit.replaceWith(money.readonly); }, blur: v => { this.campaign!.money = v; this.saveVariables(); money.edit.replaceWith(money.readonly); }, min: 0, class: 'w-24' }),
|
edit: numberpicker({ defaultValue: this.campaign.money, change: v => { this.campaign!.money = v; this.saveVariables(); money.edit.replaceWith(money.readonly); }, blur: v => { this.campaign!.money = v; this.saveVariables(); money.edit.replaceWith(money.readonly); }, min: 0, class: 'w-24' }),
|
||||||
};
|
}; */
|
||||||
|
|
||||||
return [
|
return [
|
||||||
div('flex flex-col gap-2', [
|
div('flex flex-col gap-2', [
|
||||||
div('flex flex-row justify-between items-center', [
|
div('flex flex-row justify-between items-center', [
|
||||||
div('flex flex-row justify-end items-center gap-8', [
|
div('flex flex-row justify-end items-center gap-8', [
|
||||||
div('flex flex-row gap-1 items-center', [ span('italic text-sm', 'Argent'), this.user.value && this.user.value.id === this.campaign.owner.id ? money.readonly : div('cursor-pointer px-2 py-px flex flex-row gap-1 items-center', [ span('text-lg font-bold', () => this.campaign!.money.toLocaleString(undefined, { useGrouping: true })), icon('ph:coin', { width: 16, height: 16 }) ]) ]),
|
div('flex flex-row gap-1 items-center', [ span('italic text-sm', 'Argent'), /* this.user.value && this.user.value.id === this.campaign.owner.id ? money.readonly : div('cursor-pointer px-2 py-px flex flex-row gap-1 items-center', [ span('text-lg font-bold', () => this.campaign!.money.toLocaleString(undefined, { useGrouping: true })), icon('ph:coin', { width: 16, height: 16 }) ]) */text("TODO") ]),
|
||||||
])
|
])
|
||||||
]),
|
]),
|
||||||
div('flex flex-col flex-1 divide-y divide-light-35 dark:divide-dark-35', { list: this.campaign.items, render: (e, _c) => {
|
div('flex flex-col flex-1 divide-y divide-light-35 dark:divide-dark-35', { list: this.campaign.items, render: (e, _c) => {
|
||||||
@@ -264,7 +264,7 @@ export class CampaignSheet
|
|||||||
|
|
||||||
if(!item) return;
|
if(!item) return;
|
||||||
|
|
||||||
const itempower = () => (item.powercost ?? 0) + (e.enchantments?.reduce((_p, _v) => (config.enchantments[_v]?.power ?? 0) + _p, 0) ?? 0);
|
const itempower = () => (item.powercost ?? 0) + (e.improvements?.reduce((_p, _v) => (config.improvements[_v]?.power ?? 0) + _p, 0) ?? 0);
|
||||||
|
|
||||||
const price = div(() => ['flex flex-row min-w-16 gap-2 justify-between items-center px-2', { 'cursor-help': e.amount > 1 && !!item.price }], [ icon('ph:coin', { 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.price }), () => item.price ? `${item.price * e.amount}` : '-') ]);
|
const price = div(() => ['flex flex-row min-w-16 gap-2 justify-between items-center px-2', { 'cursor-help': e.amount > 1 && !!item.price }], [ icon('ph:coin', { 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.price }), () => item.price ? `${item.price * e.amount}` : '-') ]);
|
||||||
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}` : '-') ]);
|
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}` : '-') ]);
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,4 +1,4 @@
|
|||||||
import type { Ability, Alignment, ArmorConfig, ArmorState, Character, CharacterConfig, CompiledCharacter, DamageType, EnchantementConfig, FeatureEquipment, FeatureID, FeatureItem, FeatureList, FeatureState, FeatureValue, ItemConfig, ItemState, Level, MainStat, MundaneState, Resistance, SpellConfig, SpellElement, SpellType, TrainingLevel, TreeStructure, WeaponConfig, WeaponState, WeaponType, WondrousState } from "~/types/character";
|
import type { Ability, Alignment, ArmorConfig, ArmorState, Character, CharacterConfig, CompiledCharacter, DamageType, ImprovementConfig, FeatureEquipment, FeatureID, FeatureItem, FeatureList, FeatureState, FeatureValue, ItemConfig, ItemState, Level, MainStat, MundaneState, Resistance, SpellConfig, SpellElement, SpellType, TrainingLevel, TreeStructure, WeaponConfig, WeaponState, WeaponType, WondrousState, CraftingType } 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";
|
||||||
@@ -27,6 +27,7 @@ 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 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 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", "improvised"] as const;
|
export const WEAPON_TYPES = ["light", "shield", "heavy", "classic", "throw", "natural", "twohanded", "finesse", "reach", "projectile", "improvised"] as const;
|
||||||
|
export const CRAFTING_TYPES = ["crafter", "armorer", "enchanter", "brewerer"] as const;
|
||||||
|
|
||||||
export const defaultCharacter: Character = {
|
export const defaultCharacter: Character = {
|
||||||
id: -1,
|
id: -1,
|
||||||
@@ -246,7 +247,7 @@ export const CharacterNotesValidation = z.object({
|
|||||||
export const ItemStateValidation = z.object({
|
export const ItemStateValidation = z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
amount: z.number().min(1),
|
amount: z.number().min(1),
|
||||||
enchantments: z.array(z.string()).optional(),
|
improvements: z.array(z.string()).optional(),
|
||||||
charges: z.number().optional(),
|
charges: z.number().optional(),
|
||||||
equipped: z.boolean().optional(),
|
equipped: z.boolean().optional(),
|
||||||
state: z.any().optional(),
|
state: z.any().optional(),
|
||||||
@@ -336,7 +337,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] }] });
|
Object.entries(value.abilities).forEach(e => this._buffer[`abilities/${e[0]}`] = { value: 0, _dirty: true, min: -Infinity, list: [{ id: '', operation: 'add', value: e[1] }] });
|
||||||
|
|
||||||
value.variables.items.forEach(e => this.enchant(e));
|
value.variables.items.forEach(e => this.improve(e));
|
||||||
|
|
||||||
reactivity(() => value.variables.transformed, (v) => {
|
reactivity(() => value.variables.transformed, (v) => {
|
||||||
if(this._character && this._result && value.aspect && config.aspects[value.aspect])
|
if(this._character && this._result && value.aspect && config.aspects[value.aspect])
|
||||||
@@ -424,25 +425,25 @@ export class CharacterCompiler
|
|||||||
}
|
}
|
||||||
get power()
|
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) ?? 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.improvements?.reduce((_p, _v) => (config.improvements[_v]?.power ?? 0) + _p, 0) ?? 0) * v.amount), 0) ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
enchant(item: ItemState)
|
improve(item: ItemState)
|
||||||
{
|
{
|
||||||
if(item.equipped)
|
if(item.equipped)
|
||||||
{
|
{
|
||||||
config.items[item.id]?.effects?.filter(e => e.category !== 'value' || !e.property.startsWith('item/'))?.forEach(f => this.apply(f as FeatureValue | FeatureState | FeatureList))
|
config.items[item.id]?.effects?.filter(e => e.category !== 'value' || !e.property.startsWith('item/'))?.forEach(f => this.apply(f as FeatureValue | FeatureState | FeatureList))
|
||||||
item.enchantments?.forEach(e => config.enchantments[e]?.effect.filter(e => e.category !== 'value' || !e.property.startsWith('item')).forEach(f => this.apply(f as FeatureValue | FeatureState | FeatureList)));
|
item.improvements?.forEach(e => config.improvements[e]?.effect.filter(e => e.category !== 'value' || !e.property.startsWith('item')).forEach(f => this.apply(f as FeatureValue | FeatureState | FeatureList)));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
config.items[item.id]?.effects?.filter(e => e.category !== 'value' || !e.property.startsWith('item/'))?.forEach(f => this.undo(f as FeatureValue | FeatureState | FeatureList))
|
config.items[item.id]?.effects?.filter(e => e.category !== 'value' || !e.property.startsWith('item/'))?.forEach(f => this.undo(f as FeatureValue | FeatureState | FeatureList))
|
||||||
item.enchantments?.forEach(e => config.enchantments[e]?.effect.filter(e => e.category !== 'value' || !e.property.startsWith('item')).forEach(f => this.undo(f as FeatureValue | FeatureState | FeatureList)));
|
item.improvements?.forEach(e => config.improvements[e]?.effect.filter(e => e.category !== 'value' || !e.property.startsWith('item')).forEach(f => this.undo(f as FeatureValue | FeatureState | FeatureList)));
|
||||||
}
|
}
|
||||||
|
|
||||||
item.buffer ??= {} as Record<string, PropertySum>;
|
item.buffer ??= {} as Record<string, PropertySum>;
|
||||||
Object.keys(item.buffer).forEach(e => item.buffer![e]!.list = []);
|
Object.keys(item.buffer).forEach(e => item.buffer![e]!.list = []);
|
||||||
item.enchantments?.forEach(e => (config.enchantments[e]?.effect.filter(e => e.category === 'value' && e.property.startsWith('item')) as FeatureEquipment[]).forEach(feature => {
|
item.improvements?.forEach(e => (config.improvements[e]?.effect.filter(e => e.category === 'value' && e.property.startsWith('item')) as FeatureEquipment[]).forEach(feature => {
|
||||||
const property = feature.property.substring(5);
|
const property = feature.property.substring(5);
|
||||||
item.buffer![property] ??= { list: [], value: 0, _dirty: true, min: -Infinity };
|
item.buffer![property] ??= { list: [], value: 0, _dirty: true, min: -Infinity };
|
||||||
|
|
||||||
@@ -1438,8 +1439,9 @@ type Category = ItemConfig['category'];
|
|||||||
type Rarity = ItemConfig['rarity'];
|
type Rarity = ItemConfig['rarity'];
|
||||||
export const colorByRarity: Record<Rarity, string> = {
|
export const colorByRarity: Record<Rarity, string> = {
|
||||||
'common': 'text-light-100 dark:text-dark-100',
|
'common': 'text-light-100 dark:text-dark-100',
|
||||||
'uncommon': 'text-light-cyan dark:text-dark-cyan',
|
'uncommon': 'text-light-green dark:text-dark-green',
|
||||||
'rare': 'text-light-purple dark:text-dark-purple',
|
'rare': 'text-light-cyan dark:text-dark-cyan',
|
||||||
|
'veryrare': 'text-light-purple dark:text-dark-purple',
|
||||||
'legendary': 'text-light-orange dark:text-dark-orange'
|
'legendary': 'text-light-orange dark:text-dark-orange'
|
||||||
}
|
}
|
||||||
export const weaponTypeTexts: Record<WeaponType, string> = {
|
export const weaponTypeTexts: Record<WeaponType, string> = {
|
||||||
@@ -1468,10 +1470,17 @@ export const categoryText: Record<Category, string> = {
|
|||||||
};
|
};
|
||||||
export const rarityText: Record<Rarity, string> = {
|
export const rarityText: Record<Rarity, string> = {
|
||||||
'common': 'Commun',
|
'common': 'Commun',
|
||||||
'uncommon': 'Atypique',
|
'uncommon': 'Peu commun',
|
||||||
'rare': 'Rare',
|
'rare': 'Rare',
|
||||||
|
'veryrare': 'Très rare',
|
||||||
'legendary': 'Légendaire'
|
'legendary': 'Légendaire'
|
||||||
};
|
};
|
||||||
|
export const craftingText: Record<CraftingType, string> = {
|
||||||
|
'crafter': 'Fabrication',
|
||||||
|
'armorer': 'Armurerie',
|
||||||
|
'enchanter': 'Enchantement',
|
||||||
|
'brewerer': 'Alchimie',
|
||||||
|
}
|
||||||
export const subnameFactory = (item: ItemConfig, state?: ItemState): string[] => {
|
export const subnameFactory = (item: ItemConfig, state?: ItemState): string[] => {
|
||||||
let result = [];
|
let result = [];
|
||||||
switch(item.category)
|
switch(item.category)
|
||||||
@@ -1489,13 +1498,13 @@ export const subnameFactory = (item: ItemConfig, state?: ItemState): string[] =>
|
|||||||
result = ['Objet magique'];
|
result = ['Objet magique'];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if(state && state.enchantments !== undefined && state.enchantments.length > 0) result.push('Enchanté');
|
if(state && state.improvements !== undefined && state.improvements.length > 0) result.push('Amélioré');
|
||||||
if(item.consummable) result.push('Consommable');
|
if(item.consummable) result.push('Consommable');
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
export const stateFactory = (item: ItemConfig) => {
|
export const stateFactory = (item: ItemConfig) => {
|
||||||
const state = { id: item.id, amount: 1, charges: item.charge, enchantments: [], equipped: item.equippable ? false : undefined } as ItemState;
|
const state = { id: item.id, amount: 1, charges: item.charge, improvements: [], equipped: item.equippable ? false : undefined } as ItemState;
|
||||||
switch(item.category)
|
switch(item.category)
|
||||||
{
|
{
|
||||||
case 'armor':
|
case 'armor':
|
||||||
@@ -2220,18 +2229,13 @@ export class CharacterSheet extends CharacterCompiler
|
|||||||
{
|
{
|
||||||
const items = this.compiled.variables.items;
|
const items = this.compiled.variables.items;
|
||||||
const panel = this.itemsPanel();
|
const panel = this.itemsPanel();
|
||||||
const enchant = this.enchantPanel();
|
const improve = this.improvePanel();
|
||||||
|
|
||||||
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', () => this.compiled.variables.money.toLocaleString(undefined, { useGrouping: true })), icon('ph:coin', { width: 16, height: 16 }) ]),
|
|
||||||
edit: numberpicker({ defaultValue: this.compiled.variables.money, change: v => { this.compiled.variables.money = v; this.saveVariables(); money.edit.replaceWith(money.readonly); }, blur: v => { this.compiled.variables.money = v; this.saveVariables(); money.edit.replaceWith(money.readonly); }, min: 0, class: 'w-24' }),
|
|
||||||
};
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
div('flex flex-col gap-2', [
|
div('flex flex-col gap-2', [
|
||||||
div('flex flex-row justify-between items-center', [
|
div('flex flex-row justify-between items-center', [
|
||||||
div('flex flex-row justify-end items-center gap-8', [
|
div('flex flex-row justify-end items-center gap-8', [
|
||||||
div('flex flex-row gap-1 items-center', [ span('italic text-sm', 'Argent'), money.readonly ]),
|
div('flex flex-row gap-1 items-center', [ span('italic text-sm', 'Argent'), text("TODO") ]),
|
||||||
]),
|
]),
|
||||||
div('flex flex-row justify-end items-center gap-8', [
|
div('flex flex-row justify-end items-center gap-8', [
|
||||||
dom('span', { class: () => ['italic text-sm', { 'text-light-red dark:text-dark-red': this.power > this.compiled.itempower }], text: () => `Puissance magique: ${this.power}/${this.compiled.itempower}` }),
|
dom('span', { class: () => ['italic text-sm', { 'text-light-red dark:text-dark-red': this.power > this.compiled.itempower }], text: () => `Puissance magique: ${this.power}/${this.compiled.itempower}` }),
|
||||||
@@ -2246,13 +2250,13 @@ export class CharacterSheet extends CharacterCompiler
|
|||||||
|
|
||||||
if(!item) return;
|
if(!item) return;
|
||||||
|
|
||||||
const itempower = () => (item.powercost ?? 0) + (e.enchantments?.reduce((_p, _v) => (config.enchantments[_v]?.power ?? 0) + _p, 0) ?? 0);
|
const itempower = () => (item.powercost ?? 0) + (e.improvements?.reduce((_p, _v) => (config.improvements[_v]?.power ?? 0) + _p, 0) ?? 0);
|
||||||
|
|
||||||
const price = div(() => ['flex flex-row min-w-16 gap-2 justify-between items-center px-2', { 'cursor-help': e.amount > 1 && !!item.price }], [ icon('ph:coin', { 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.price }), () => item.price ? `${item.price * e.amount}` : '-') ]);
|
const price = div(() => ['flex flex-row min-w-16 gap-2 justify-between items-center px-2', { 'cursor-help': e.amount > 1 && !!item.price }], [ icon('ph:coin', { 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.price }), () => item.price ? `${item.price * e.amount}` : '-') ]);
|
||||||
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}` : '-') ]);
|
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(() => [
|
return foldable(() => [
|
||||||
markdown(getText(item.description)),
|
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 gap-1', { list: () => e.improvements!.map(e => config.improvements[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', [
|
div('flex flex-row justify-center gap-1', [
|
||||||
this.character.campaign ? button(text('Partager'), () => {
|
this.character.campaign ? button(text('Partager'), () => {
|
||||||
|
|
||||||
@@ -2276,8 +2280,8 @@ export class CharacterSheet extends CharacterCompiler
|
|||||||
|
|
||||||
this.saveVariables();
|
this.saveVariables();
|
||||||
}, 'p-1'),
|
}, 'p-1'),
|
||||||
() => !item.capacity ? undefined : button(text("Enchanter"), () => {
|
() => !item.capacity ? undefined : button(text("Améliorer"), () => {
|
||||||
enchant.show(e);
|
improve.show(e);
|
||||||
}, 'px-2 text-sm h-5 box-content'),
|
}, 'px-2 text-sm h-5 box-content'),
|
||||||
])
|
])
|
||||||
], [ div('flex flex-row justify-between', [
|
], [ div('flex flex-row justify-between', [
|
||||||
@@ -2287,7 +2291,7 @@ export class CharacterSheet extends CharacterCompiler
|
|||||||
return Toaster.add({ content: "Vous ne pouvez equipper qu'une seule armure à la fois.", duration: 5000, timer: true, type: 'info' }), false;
|
return Toaster.add({ content: "Vous ne pouvez equipper qu'une seule armure à la fois.", duration: 5000, timer: true, type: 'info' }), false;
|
||||||
|
|
||||||
e.equipped = v;
|
e.equipped = v;
|
||||||
this.enchant(e);
|
this.improve(e);
|
||||||
}, class: { container: '!w-5 !h-5' } }) : undefined,
|
}, 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))) ]),
|
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('/')})`) ]) :
|
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('/')})`) ]) :
|
||||||
@@ -2373,32 +2377,34 @@ export class CharacterSheet extends CharacterCompiler
|
|||||||
container.setAttribute('data-state', 'inactive');
|
container.setAttribute('data-state', 'inactive');
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
enchantPanel()
|
improvePanel()
|
||||||
{
|
{
|
||||||
const current = reactive({
|
const current = reactive({
|
||||||
item: undefined as ItemState | undefined,
|
item: undefined as ItemState | undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
const restrict = (enchant: EnchantementConfig, id?: string) => {
|
const restrict = (improvement: ImprovementConfig, id?: string) => {
|
||||||
if(!id) return true;
|
if(!id) return true;
|
||||||
|
|
||||||
const item = config.items[id]!;
|
const item = config.items[id]!;
|
||||||
if(!enchant.restrictions)
|
if(!improvement.restrictions)
|
||||||
return true;
|
return true;
|
||||||
if(enchant.restrictions.includes(item.category))
|
if(improvement.restrictions[item.category])
|
||||||
return true;
|
return true;
|
||||||
else if(item.category === 'armor' && enchant.restrictions.includes(`armor/${item.type}`))
|
else if(item.category === 'armor' && improvement.restrictions[`armor/${item.type}`])
|
||||||
return true;
|
return true;
|
||||||
else if(item.category === 'weapon' && item.type.some(e => enchant.restrictions!.includes(`weapon/${e}`)))
|
else if(item.category === 'weapon' && item.type.some(e => improvement.restrictions![`weapon/${e}`]))
|
||||||
|
return true;
|
||||||
|
else if(improvement.restrictions[item.id])
|
||||||
return true;
|
return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const itempower = () => current.item && config.items[current.item.id] !== undefined ? ((config.items[current.item.id]!.powercost ?? 0) + (current.item.enchantments?.reduce((_p, _v) => (config.enchantments[_v]?.power ?? 0) + _p, 0) ?? 0)) : 0;
|
const itempower = () => current.item && config.items[current.item.id] !== undefined ? ((config.items[current.item.id]!.powercost ?? 0) + (current.item.improvements?.reduce((_p, _v) => (config.improvements[_v]?.power ?? 0) + _p, 0) ?? 0)) : 0;
|
||||||
|
|
||||||
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]", [
|
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", [
|
div("flex flex-row justify-between items-center mb-4", [
|
||||||
dom("h2", { class: "text-xl font-bold", text: "Enchantements" }),
|
dom("h2", { class: "text-xl font-bold", text: "Améliorations" }),
|
||||||
div('flex flex-row gap-8 items-center justify-end', [
|
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': 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.power > this.compiled!.itempower }], text: () => `Puissance du personnage: ${this.power}/${this.compiled.itempower}` }),
|
dom('span', { class: () => ['italic text-sm', { 'text-light-red dark:text-dark-red': this.power > this.compiled!.itempower }], text: () => `Puissance du personnage: ${this.power}/${this.compiled.itempower}` }),
|
||||||
@@ -2408,20 +2414,20 @@ export class CharacterSheet extends CharacterCompiler
|
|||||||
}, "p-1"), "Fermer", "left")
|
}, "p-1"), "Fermer", "left")
|
||||||
])
|
])
|
||||||
]),
|
]),
|
||||||
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('grid grid-cols-1 -my-2 overflow-y-auto gap-1', { list: () => Object.values(config.improvements).filter(e => restrict(e, current.item?.id)), render: (improve, _c) => _c ?? foldable(() => [ markdown(getText(improve.description)) ], [
|
||||||
div('flex flex-row justify-between', [
|
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 gap-4', [ span('text-lg', improve.name) ]),
|
||||||
div('flex flex-row items-center divide-x divide-light-50 dark:divide-dark-50 divide-dashed px-2 gap-4', [
|
div('flex flex-row items-center divide-x divide-light-50 dark:divide-dark-50 divide-dashed px-2 gap-4', [
|
||||||
enchant.cursed ? span('italic text-sm text-light-purple dark:text-dark-purple', `Malédiction`) : undefined,
|
improve.cursed ? span('italic text-sm text-light-purple dark:text-dark-purple', `Malédiction`) : undefined,
|
||||||
span('italic text-sm', `Puissance magique: ${enchant.power}`),
|
span('italic text-sm', `Puissance magique: ${improve.power}`),
|
||||||
button(icon(() => current.item?.enchantments?.includes(enchant.id) ? 'radix-icons:minus' : 'radix-icons:plus', { width: 16, height: 16 }), () => {
|
button(icon(() => current.item?.improvements?.includes(improve.id) ? 'radix-icons:minus' : 'radix-icons:plus', { width: 16, height: 16 }), () => {
|
||||||
const idx = current.item!.enchantments?.findIndex(e => e === enchant.id) ?? -1;
|
const idx = current.item!.improvements?.findIndex(e => e === improve.id) ?? -1;
|
||||||
if(idx === -1)
|
if(idx === -1)
|
||||||
current.item!.enchantments?.push(enchant.id);
|
current.item!.improvements?.push(improve.id);
|
||||||
else
|
else
|
||||||
current.item!.enchantments?.splice(idx, 1);
|
current.item!.improvements?.splice(idx, 1);
|
||||||
|
|
||||||
this.enchant(current.item!);
|
this.improve(current.item!);
|
||||||
}, 'p-1 !border-solid !border-r'),
|
}, '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' } })
|
])], { open: false, class: { icon: 'px-2', container: 'border border-light-35 dark:border-dark-35 p-1 gap-2', content: 'px-2 pb-1' } })
|
||||||
|
|||||||
@@ -517,7 +517,7 @@ export function numberpicker(settings?: { defaultValue?: Reactive<number>, chang
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const field = dom("input", { attributes: { disabled: settings?.disabled }, class: [`w-14 mx-4 caret-light-50 dark:caret-dark-50 text-light-100 dark:text-dark-100 placeholder:text-light-50 dark:placeholder:text-dark-50 bg-light-20 dark:bg-dark-20 outline-none px-3 py-1 focus:shadow-raw transition-[box-shadow] focus:shadow-light-40 dark:focus:shadow-dark-40 border border-light-35 dark:border-dark-35 hover:border-light-50 dark:hover:border-dark-50 disabled:shadow-none disabled:bg-light-20 dark:disabled:bg-dark-20 disabled:border-dashed disabled:border-light-35 dark:disabled:border-dark-35 disabled:border-2`, settings?.class], listeners: {
|
const field = dom("input", { attributes: { disabled: settings?.disabled }, class: [`w-14 caret-light-50 dark:caret-dark-50 text-light-100 dark:text-dark-100 placeholder:text-light-50 dark:placeholder:text-dark-50 bg-light-20 dark:bg-dark-20 outline-none px-3 py-1 focus:shadow-raw transition-[box-shadow] focus:shadow-light-40 dark:focus:shadow-dark-40 border border-light-35 dark:border-dark-35 hover:border-light-50 dark:hover:border-dark-50 disabled:shadow-none disabled:bg-light-20 dark:disabled:bg-dark-20 disabled:border-dashed disabled:border-light-35 dark:disabled:border-dark-35 disabled:border-2`, settings?.class], listeners: {
|
||||||
input: () => validateAndChange(parseInt(field.value.trim().toLowerCase().normalize().replace(/[a-z,.]/g, ""), 10)) && settings?.input && settings.input(storedValue),
|
input: () => validateAndChange(parseInt(field.value.trim().toLowerCase().normalize().replace(/[a-z,.]/g, ""), 10)) && settings?.input && settings.input(storedValue),
|
||||||
keydown: (e: KeyboardEvent) => {
|
keydown: (e: KeyboardEvent) => {
|
||||||
if(field.disabled)
|
if(field.disabled)
|
||||||
@@ -821,6 +821,14 @@ export function floater(container: HTMLElement, content: NodeChildren | (() => N
|
|||||||
|
|
||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
|
export function tagpicker<T extends string>(list?: Reactive<T[]>, settings?: { defaultValue?: T[], class?: { container?: string, tag?: string }, disabled?: Reactive<boolean>, onUpdate?: () => boolean, onAdd?: (pick: T) => boolean, onRemove?: (pick: T) => boolean })
|
||||||
|
{
|
||||||
|
const value = settings?.defaultValue ?? [];
|
||||||
|
const _input = dom('input', { class: 'appearence-none bg-transparent border-none outline-none', attributes: { type: 'text', tabindex: '0' }, listeners: { focus: () => content.toggleAttribute('data-focused', true), blur: () => content.toggleAttribute('data-focused', false) }}), float = followermenu(_input, [ div('flex flex-col', { list: list, render: (item, _c) => _c ?? text(item) }) ]);
|
||||||
|
const content = dom('div', { class: ['border border-light-35 dark:border-dark-35 p-2 gap-2 flex flex-row items-center', settings?.class?.container], attributes: { tabindex: '0' }, listeners: { focus: () => _input.focus(), click: () => _input.focus() } }, [ div('flex flex-row gap-2', { list: () => value, render: (restrict, _c) => _c ?? div('') }), _input ]);
|
||||||
|
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ToastConfig
|
export interface ToastConfig
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { crosshairCursor, Decoration, dropCursor, EditorView, keymap, ViewPlugin
|
|||||||
import { Annotation, Compartment, EditorState, Prec, SelectionRange, StateField, type Extension, type Range } from '@codemirror/state';
|
import { Annotation, Compartment, EditorState, Prec, SelectionRange, StateField, type Extension, type Range } from '@codemirror/state';
|
||||||
import { defaultKeymap, history, historyKeymap, standardKeymap } from '@codemirror/commands';
|
import { defaultKeymap, history, historyKeymap, standardKeymap } from '@codemirror/commands';
|
||||||
import { bracketMatching, HighlightStyle, indentOnInput, syntaxHighlighting, syntaxTree } from '@codemirror/language';
|
import { bracketMatching, HighlightStyle, indentOnInput, syntaxHighlighting, syntaxTree } from '@codemirror/language';
|
||||||
import { search, searchKeymap } from '@codemirror/search';
|
import { searchKeymap } from '@codemirror/search';
|
||||||
import { acceptCompletion, autocompletion, closeBrackets, closeBracketsKeymap, completionKeymap } from '@codemirror/autocomplete';
|
import { acceptCompletion, autocompletion, closeBrackets, closeBracketsKeymap, completionKeymap } from '@codemirror/autocomplete';
|
||||||
import { markdown, markdownLanguage } from '@codemirror/lang-markdown';
|
import { markdown, markdownLanguage } from '@codemirror/lang-markdown';
|
||||||
import { IterMode, Tree, type SyntaxNodeRef } from '@lezer/common';
|
import { IterMode, Tree, type SyntaxNodeRef } from '@lezer/common';
|
||||||
@@ -14,8 +14,8 @@ import renderMarkdown from '~~/shared/markdown';
|
|||||||
import prose, { a, blockquote, tag, h1, h2, h3, h4, h5, hr, li, small, table, td, th, callout } from "#shared/proses";
|
import prose, { a, blockquote, tag, h1, h2, h3, h4, h5, hr, li, small, table, td, th, callout } from "#shared/proses";
|
||||||
import { tagTag, tag as tagExtension } from './grammar/tag.extension';
|
import { tagTag, tag as tagExtension } from './grammar/tag.extension';
|
||||||
import { WeakerSet } from './general';
|
import { WeakerSet } from './general';
|
||||||
import { button, numberpicker } from './components';
|
import { button } from './components';
|
||||||
import { contextmenu, followermenu } from './floating';
|
import { followermenu } from './floating';
|
||||||
|
|
||||||
const External = Annotation.define<boolean>();
|
const External = Annotation.define<boolean>();
|
||||||
const Hidden = Decoration.mark({ class: 'hidden' });
|
const Hidden = Decoration.mark({ class: 'hidden' });
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import type { Ability, ArmorConfig, AspectConfig, CharacterConfig, CommonItemConfig, DamageType, Feature, FeatureChoice, FeatureEquipment, FeatureItem, FeatureList, FeatureState, FeatureTree, FeatureValue, ItemConfig, Level, MainStat, MundaneConfig, RaceConfig, Resistance, SpellConfig, TrainingLevel, WeaponConfig, WeaponType, WondrousConfig } from "~/types/character";
|
import type { Ability, ArmorConfig, AspectConfig, CharacterConfig, CommonItemConfig, CraftingType, DamageType, Feature, FeatureChoice, FeatureEquipment, FeatureItem, FeatureList, FeatureState, FeatureTree, FeatureValue, ImprovementConfig, ItemConfig, Level, MainStat, MundaneConfig, RaceConfig, Resistance, SpellConfig, TrainingLevel, WeaponConfig, WeaponType, WondrousConfig } from "~/types/character";
|
||||||
import { div, dom, icon, span, text, type NodeChildren } from "#shared/dom";
|
import { div, dom, icon, span, text, type NodeChildren } from "#shared/dom";
|
||||||
import { MarkdownEditor } from "#shared/editor";
|
import { MarkdownEditor } from "#shared/editor";
|
||||||
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";
|
import { button, checkbox, combobox, foldable, input, multiselect, numberpicker, optionmenu, select, tabgroup, table, tagpicker, toggle, type Option } from "#shared/components";
|
||||||
import { confirm, contextmenu, fullblocker, tooltip } from "#shared/floating";
|
import { confirm, contextmenu, fullblocker, tooltip } from "#shared/floating";
|
||||||
import { ABILITIES, abilityTexts, ALIGNMENTS, alignmentTexts, colorByRarity, DAMAGE_TYPES, damageTypeTexts, elementTexts, LEVELS, MAIN_STATS, mainStatShortTexts, mainStatTexts, masteryTexts, rarityText, RESISTANCES, resistanceTexts, SPELL_ELEMENTS, SPELL_TYPES, spellTypeTexts, subnameFactory, weaponTypeTexts } from "#shared/character";
|
import { ABILITIES, abilityTexts, ALIGNMENTS, alignmentTexts, colorByRarity, craftingText, DAMAGE_TYPES, damageTypeTexts, elementTexts, LEVELS, MAIN_STATS, mainStatShortTexts, mainStatTexts, masteryTexts, rarityText, RESISTANCES, resistanceTexts, SPELL_ELEMENTS, SPELL_TYPES, spellTypeTexts, subnameFactory, weaponTypeTexts } from "#shared/character";
|
||||||
import characterConfig from "#shared/character-config.json";
|
import characterConfig from "#shared/character-config.json";
|
||||||
import { getID } from "#shared/general";
|
import { getID } from "#shared/general";
|
||||||
import markdown, { markdownReference, renderMDAsText } from "#shared/markdown";
|
import markdown, { markdownReference, renderMDAsText } from "#shared/markdown";
|
||||||
@@ -32,6 +32,7 @@ export class HomebrewBuilder
|
|||||||
{ id: 'aspects', title: [ text("Aspects") ], content: () => this.aspects() },
|
{ id: 'aspects', title: [ text("Aspects") ], content: () => this.aspects() },
|
||||||
{ id: 'actions', title: [ text("Actions") ], content: () => this.actions() },
|
{ id: 'actions', title: [ text("Actions") ], content: () => this.actions() },
|
||||||
{ id: 'items', title: [ text("Objets") ], content: () => this.items() },
|
{ id: 'items', title: [ text("Objets") ], content: () => this.items() },
|
||||||
|
{ id: 'improvements', title: [ text("Améliorations") ], content: () => this.improvements() },
|
||||||
{ id: 'trees', title: [ text("Arbres") ], content: () => this.trees() },
|
{ 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' } });
|
], { 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' } });
|
||||||
|
|
||||||
@@ -512,6 +513,7 @@ export class HomebrewBuilder
|
|||||||
rarity: 'common',
|
rarity: 'common',
|
||||||
equippable: false,
|
equippable: false,
|
||||||
consummable: false,
|
consummable: false,
|
||||||
|
craft: { natural: 0, mineral: 0, processed: 0, magical: 0 },
|
||||||
};
|
};
|
||||||
switch(category)
|
switch(category)
|
||||||
{
|
{
|
||||||
@@ -530,7 +532,7 @@ export class HomebrewBuilder
|
|||||||
config.items[item.id!] = item;
|
config.items[item.id!] = item;
|
||||||
};
|
};
|
||||||
const remove = (item: ItemConfig) => {
|
const remove = (item: ItemConfig) => {
|
||||||
confirm(`Voulez vous vraiment supprimer l'effet "${item.name}" ?`).then(e => {
|
confirm(`Voulez vous vraiment supprimer l'objet "${item.name}" ?`).then(e => {
|
||||||
if(e)
|
if(e)
|
||||||
{
|
{
|
||||||
delete config.texts[item.description];
|
delete config.texts[item.description];
|
||||||
@@ -568,6 +570,59 @@ export class HomebrewBuilder
|
|||||||
}
|
}
|
||||||
}) ] ) ];
|
}) ] ) ];
|
||||||
}
|
}
|
||||||
|
improvements()
|
||||||
|
{
|
||||||
|
const defaultImprovement = (): ImprovementConfig => {
|
||||||
|
return {
|
||||||
|
id: getID(),
|
||||||
|
name: '',
|
||||||
|
description: getID(), // i18nID
|
||||||
|
craft: {},
|
||||||
|
rarity: 'common',
|
||||||
|
cursed: false,
|
||||||
|
power: 0,
|
||||||
|
effect: [],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const add = () => {
|
||||||
|
const improvement = defaultImprovement();
|
||||||
|
setText(improvement.description, '');
|
||||||
|
config.improvements[improvement.id!] = improvement;
|
||||||
|
};
|
||||||
|
const remove = (improvement: ImprovementConfig) => {
|
||||||
|
confirm(`Voulez vous vraiment supprimer l'amélioration "${improvement.name}" ?`).then(e => {
|
||||||
|
if(e)
|
||||||
|
{
|
||||||
|
delete config.texts[improvement.description];
|
||||||
|
delete config.improvements[improvement.id];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const edit = (improvement: ImprovementConfig) => {
|
||||||
|
ImprovementPanel.edit(improvement).then(f => {
|
||||||
|
Object.assign(config.improvements[f.id]!, f);
|
||||||
|
}).catch((e) => {});
|
||||||
|
}
|
||||||
|
return [ div('flex px-8 py-4 flex-col gap-4', [ div('flex flex-row-reverse', [ button(icon('radix-icons:plus'), add, 'p-1') ]), div('grid grid-cols-2 gap-1', {
|
||||||
|
list: () => Object.keys(config.improvements),
|
||||||
|
render: (e, _c) => {
|
||||||
|
const improvement = config.improvements[e];
|
||||||
|
|
||||||
|
if(!improvement) return;
|
||||||
|
|
||||||
|
return _c ?? div('border border-light-35 dark:border-dark-35 p-1 gap-2', [ div('flex flex-row justify-between', [
|
||||||
|
div('flex flex-row items-center gap-4 ps-2', [ span(() => [colorByRarity[improvement.rarity], 'text-lg font-semibold'], () => improvement.name) ]),
|
||||||
|
div('flex flex-row gap-1', [
|
||||||
|
div('flex flex-row items-center divide-x divide-light-50 dark:divide-dark-50 divide-dashed px-2', [
|
||||||
|
div('flex flex-row w-12 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('', () => improvement.power) ]),
|
||||||
|
]),
|
||||||
|
button(icon('radix-icons:pencil-2'), () => edit(config.improvements[e]!), 'p-1'),
|
||||||
|
button(icon('radix-icons:trash'), () => remove(config.improvements[e]!), 'p-1'),
|
||||||
|
])
|
||||||
|
]), div('px-2 pb-1', [ () => markdown(getText(improvement.description)) ]) ]);
|
||||||
|
}
|
||||||
|
}) ] ) ];
|
||||||
|
}
|
||||||
trees()
|
trees()
|
||||||
{
|
{
|
||||||
const add = () => {
|
const add = () => {
|
||||||
@@ -976,8 +1031,13 @@ export class ItemPanel
|
|||||||
}, 'p-1'), 'Annuler', 'left'),
|
}, 'p-1'), 'Annuler', 'left'),
|
||||||
]),
|
]),
|
||||||
foldable([
|
foldable([
|
||||||
|
div('flex flex-row col-span-2 gap-2 items-center justify-between', [ span('flex flex-row gap-2 items-center', 'Fabrication'), div('flex flex-row items-center gap-2 w-1/2', [ select<CraftingType>(Object.entries(craftingText).map(e => ({ text: e[1], value: e[0] as CraftingType })), { defaultValue: _item.craft.ability ?? 'crafter', change: (v) => _item.craft.ability = v, class: { container: '!w-1/2' } }), numberpicker({ defaultValue: _item.craft.difficulty ?? 0, input: (v) => _item.craft.difficulty = v, class: 'w-12' }) ]), div('flex flex-row items-center gap-1', [
|
||||||
|
numberpicker({ defaultValue: _item.craft.natural, disabled: _item.craft.natural === undefined, input: (v) => _item.craft.natural = v, class: 'w-10' }),
|
||||||
|
numberpicker({ defaultValue: _item.craft.mineral, disabled: _item.craft.mineral === undefined, input: (v) => _item.craft.mineral = v, class: 'w-10' }),
|
||||||
|
numberpicker({ defaultValue: _item.craft.processed, disabled: _item.craft.processed === undefined, input: (v) => _item.craft.processed = v, class: 'w-10' }),
|
||||||
|
numberpicker({ defaultValue: _item.craft.magical, disabled: _item.craft.magical === undefined, input: (v) => _item.craft.magical = v, class: 'w-10' }),
|
||||||
|
]) ]),
|
||||||
div('flex flex-row gap-2 items-center justify-between', [ div('flex flex-row gap-2 items-center', [ checkbox({ defaultValue: _item.weight !== undefined, change: function(value) { _item.weight = value ? 0 : undefined; if(this.parentElement?.parentElement?.children[1]) { (this.parentElement.parentElement.children[1] as Element & { disabled: boolean }).disabled = !value; (this.parentElement.parentElement.children[1] as HTMLInputElement).value = value ? '0' : ''; } this.parentElement?.toggleAttribute('data-disabled', !value) }, class: { container: '!w-4 !h-4' } }), span('', 'Poids'), ]), numberpicker({ defaultValue: _item.weight, disabled: _item.weight === undefined, input: (v) => _item.weight = v, class: '!w-1/3' }), ]),
|
div('flex flex-row gap-2 items-center justify-between', [ div('flex flex-row gap-2 items-center', [ checkbox({ defaultValue: _item.weight !== undefined, change: function(value) { _item.weight = value ? 0 : undefined; if(this.parentElement?.parentElement?.children[1]) { (this.parentElement.parentElement.children[1] as Element & { disabled: boolean }).disabled = !value; (this.parentElement.parentElement.children[1] as HTMLInputElement).value = value ? '0' : ''; } this.parentElement?.toggleAttribute('data-disabled', !value) }, class: { container: '!w-4 !h-4' } }), span('', 'Poids'), ]), numberpicker({ defaultValue: _item.weight, disabled: _item.weight === undefined, input: (v) => _item.weight = v, class: '!w-1/3' }), ]),
|
||||||
div('flex flex-row gap-2 items-center justify-between', [ div('flex flex-row gap-2 items-center', [ checkbox({ defaultValue: _item.price !== undefined, change: function(value) { _item.price = value ? 0 : undefined; if(this.parentElement?.parentElement?.children[1]) { (this.parentElement.parentElement.children[1] as Element & { disabled: boolean }).disabled = !value; (this.parentElement.parentElement.children[1] as HTMLInputElement).value = value ? '0' : ''; } this.parentElement?.toggleAttribute('data-disabled', !value) }, class: { container: '!w-4 !h-4' } }), span('', 'Prix'), ]), numberpicker({ defaultValue: _item.price, disabled: _item.price === undefined, input: (v) => _item.price = v, class: '!w-1/3' }), ]),
|
|
||||||
div('flex flex-row gap-2 items-center justify-between', [ div('flex flex-row gap-2 items-center', [ checkbox({ defaultValue: _item.capacity !== undefined, change: function(value) { _item.capacity = value ? 0 : undefined; if(this.parentElement?.parentElement?.children[1]) { (this.parentElement.parentElement.children[1] as Element & { disabled: boolean }).disabled = !value; (this.parentElement.parentElement.children[1] as HTMLInputElement).value = value ? '0' : ''; } this.parentElement?.toggleAttribute('data-disabled', !value) }, class: { container: '!w-4 !h-4' } }), span('', 'Capacité magique'), ]), numberpicker({ defaultValue: _item.capacity, disabled: _item.capacity === undefined, input: (v) => _item.capacity = v, class: '!w-1/3' }), ]),
|
div('flex flex-row gap-2 items-center justify-between', [ div('flex flex-row gap-2 items-center', [ checkbox({ defaultValue: _item.capacity !== undefined, change: function(value) { _item.capacity = value ? 0 : undefined; if(this.parentElement?.parentElement?.children[1]) { (this.parentElement.parentElement.children[1] as Element & { disabled: boolean }).disabled = !value; (this.parentElement.parentElement.children[1] as HTMLInputElement).value = value ? '0' : ''; } this.parentElement?.toggleAttribute('data-disabled', !value) }, class: { container: '!w-4 !h-4' } }), span('', 'Capacité magique'), ]), numberpicker({ defaultValue: _item.capacity, disabled: _item.capacity === undefined, input: (v) => _item.capacity = v, class: '!w-1/3' }), ]),
|
||||||
div('flex flex-row gap-2 items-center justify-between', [ div('flex flex-row gap-2 items-center', [ checkbox({ defaultValue: _item.powercost !== undefined, change: function(value) { _item.powercost = value ? 0 : undefined; if(this.parentElement?.parentElement?.children[1]) { (this.parentElement.parentElement.children[1] as Element & { disabled: boolean }).disabled = !value; (this.parentElement.parentElement.children[1] as HTMLInputElement).value = value ? '0' : ''; } this.parentElement?.toggleAttribute('data-disabled', !value) }, class: { container: '!w-4 !h-4' } }), span('', 'Puissance magique'), ]), numberpicker({ defaultValue: _item.powercost, disabled: _item.powercost === undefined, input: (v) => _item.powercost = v, class: '!w-1/3' }), ]),
|
div('flex flex-row gap-2 items-center justify-between', [ div('flex flex-row gap-2 items-center', [ checkbox({ defaultValue: _item.powercost !== undefined, change: function(value) { _item.powercost = value ? 0 : undefined; if(this.parentElement?.parentElement?.children[1]) { (this.parentElement.parentElement.children[1] as Element & { disabled: boolean }).disabled = !value; (this.parentElement.parentElement.children[1] as HTMLInputElement).value = value ? '0' : ''; } this.parentElement?.toggleAttribute('data-disabled', !value) }, class: { container: '!w-4 !h-4' } }), span('', 'Puissance magique'), ]), numberpicker({ defaultValue: _item.powercost, disabled: _item.powercost === undefined, input: (v) => _item.powercost = v, class: '!w-1/3' }), ]),
|
||||||
div('flex flex-row gap-2 items-center justify-between', [ div('flex flex-row gap-2 items-center', [ checkbox({ defaultValue: _item.equippable, change: function(value) { _item.equippable = value; this.parentElement?.toggleAttribute('data-disabled', !value) }, class: { container: '!w-4 !h-4' } }), span('', 'Equipable'), ]), div('flex flex-row gap-2 items-center mx-4', [ checkbox({ defaultValue: _item.consummable, change: function(value) { _item.consummable = value; this.parentElement?.toggleAttribute('data-disabled', !value) }, class: { container: '!w-4 !h-4' } }), span('', 'Consommable'), ]) ]),
|
div('flex flex-row gap-2 items-center justify-between', [ div('flex flex-row gap-2 items-center', [ checkbox({ defaultValue: _item.equippable, change: function(value) { _item.equippable = value; this.parentElement?.toggleAttribute('data-disabled', !value) }, class: { container: '!w-4 !h-4' } }), span('', 'Equipable'), ]), div('flex flex-row gap-2 items-center mx-4', [ checkbox({ defaultValue: _item.consummable, change: function(value) { _item.consummable = value; this.parentElement?.toggleAttribute('data-disabled', !value) }, class: { container: '!w-4 !h-4' } }), span('', 'Consommable'), ]) ]),
|
||||||
@@ -1020,6 +1080,61 @@ export class ItemPanel
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
export class ImprovementPanel
|
||||||
|
{
|
||||||
|
static descriptionEditor: MarkdownEditor = new MarkdownEditor();
|
||||||
|
static render(improvement: ImprovementConfig, success: (improvement: ImprovementConfig) => void, failure: (improvement: ImprovementConfig) => void)
|
||||||
|
{
|
||||||
|
const _improvement = JSON.parse(JSON.stringify(improvement)) as ImprovementConfig;
|
||||||
|
ImprovementPanel.descriptionEditor.content = getText(_improvement.description);
|
||||||
|
ImprovementPanel.descriptionEditor.onChange = (value) => setText(_improvement.description, value);
|
||||||
|
const effectContainer = div('grid grid-cols-2 gap-4 px-2 flex-1', _improvement.effect?.map(e => new FeatureEditor(_improvement.effect!, e.id, false).container));
|
||||||
|
return dom('div', { attributes: { 'data-state': 'inactive' }, class: '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-2 text-light-100 dark:text-dark-100 p-8 transition-[width] transition-delay-[150ms]' }, [
|
||||||
|
div('flex flex-row justify-between items-center', [
|
||||||
|
tooltip(button(icon('radix-icons:check', { width: 20, height: 20 }), () => {
|
||||||
|
success!(_improvement);
|
||||||
|
ImprovementPanel.descriptionEditor.onChange = undefined;
|
||||||
|
}, 'p-1'), 'Valider', 'left'),
|
||||||
|
dom('label', { class: 'flex justify-center items-center my-2' }, [
|
||||||
|
dom('span', { class: 'pb-1 md:p-0', text: "Nom" }),
|
||||||
|
input('text', { defaultValue: _improvement.name, input: (v) => { _improvement.name = v }, class: 'w-96' })
|
||||||
|
]),
|
||||||
|
tooltip(button(icon('radix-icons:cross-1', { width: 20, height: 20 }), () => {
|
||||||
|
failure!(improvement);
|
||||||
|
ImprovementPanel.descriptionEditor.onChange = undefined;
|
||||||
|
}, 'p-1'), 'Annuler', 'left'),
|
||||||
|
]),
|
||||||
|
foldable([
|
||||||
|
div('flex flex-row gap-2 items-center justify-between', [ div('flex flex-row gap-2 items-center', [ span('', 'Puissance magique'), ]), numberpicker({ defaultValue: _improvement.power, disabled: _improvement.power === undefined, input: (v) => _improvement.power = v, class: '!w-1/3' }), ]),
|
||||||
|
div('flex flex-row gap-2 items-center justify-between', [ span('flex flex-row gap-2 items-center', 'Fabrication'), div('flex flex-row items-center gap-2 !w-2/3', [ select<CraftingType>(Object.entries(craftingText).map(e => ({ text: e[1], value: e[0] as CraftingType })), { defaultValue: _improvement.craft.ability ?? 'crafter', change: (v) => _improvement.craft.ability = v, class: { container: '!w-1/2' } }), numberpicker({ defaultValue: _improvement.craft.difficulty ?? 0, input: (v) => _improvement.craft.difficulty = v, class: 'w-12' }) ]) ]),
|
||||||
|
], [ span('text-lg font-bold', "Propriétés"), div('flex flex-row gap-2 items-center justify-between', [ div('flex flex-row gap-2 items-center', [ span('', 'Rareté'), ]), select(Object.keys(rarityText).map(e => ({ text: rarityText[e as Rarity], value: e as Rarity })), { defaultValue: _improvement.rarity, change: (v) => _improvement.rarity = v, class: { container: '!w-1/2' } }), ]) ], { class: { content: 'group-data-[active]:grid grid-cols-2 my-2 gap-4', title: 'grid grid-cols-2 gap-4 mx-2', container: 'pb-2 border-b border-light-35 dark:border-dark-35' }, open: true } ),
|
||||||
|
foldable([ div('p-1 border border-light-40 dark:border-dark-40 w-full bg-light-25 dark:bg-dark-25 min-h-48 max-h-[32rem]', [ ImprovementPanel.descriptionEditor.dom ])], [ span('text-lg font-bold px-2', "Description des effets") ], { class: { container: 'gap-4 pb-2 border-b border-light-35 dark:border-dark-35' }, open: true, }),
|
||||||
|
div('flex flex-row gap-4 items-center pb-2 border-b border-light-35 dark:border-dark-35', [ dom('h3', { class: 'text-lg font-bold', text: 'Restrictions' }), tagpicker([], { defaultValue: Object.keys(improvement.restrictions ?? {}), class: { container: 'data-[focused]:shadow-raw transition-[box-shadow] data-[focused]:shadow-light-40 dark:data-[focused]:shadow-dark-40' } }) ]),
|
||||||
|
foldable([ effectContainer ], [ dom('h3', { class: 'text-lg font-bold', text: 'Effets' }),
|
||||||
|
tooltip(button(icon('radix-icons:plus', { width: 20, height: 20 }), () => {
|
||||||
|
const f = { id: getID(), };
|
||||||
|
_improvement.effect ??= [];
|
||||||
|
_improvement.effect.push(f as any as FeatureValue | FeatureEquipment | FeatureList);
|
||||||
|
effectContainer.appendChild(new FeatureEditor(_improvement.effect, f.id, true).container);
|
||||||
|
}, 'p-1 hidden group-data-[active]:block'), 'Ajouter', 'left'),
|
||||||
|
], { class: { container: 'flex flex-col gap-2 w-full', title: 'flex flex-row justify-between px-2' } })
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
static edit(improvement: ImprovementConfig): Promise<ImprovementConfig>
|
||||||
|
{
|
||||||
|
let container: HTMLElement, close: Function;
|
||||||
|
return new Promise<ImprovementConfig>((success, failure) => {
|
||||||
|
container = ImprovementPanel.render(improvement, success, failure);
|
||||||
|
close = fullblocker([container], {
|
||||||
|
priority: true, closeWhenOutside: false,
|
||||||
|
}).close;
|
||||||
|
setTimeout(() => container.setAttribute('data-state', 'active'), 1);
|
||||||
|
}).finally(() => {
|
||||||
|
setTimeout(close, 150);
|
||||||
|
container.setAttribute('data-state', 'inactive');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const featureChoices: Option<Partial<FeatureOption>>[] = [
|
const featureChoices: Option<Partial<FeatureOption>>[] = [
|
||||||
{ text: 'PV max', value: { category: 'value', property: 'health', operation: 'add', value: 1 }, },
|
{ text: 'PV max', value: { category: 'value', property: 'health', operation: 'add', value: 1 }, },
|
||||||
|
|||||||
Reference in New Issue
Block a user