Mass updates

This commit is contained in:
2026-01-05 11:33:32 +01:00
parent 32b6cf4af7
commit 04534b2530
36 changed files with 1886 additions and 12036 deletions

View File

@@ -15,11 +15,13 @@
import { Content } from '#shared/content.util';
import * as Floating from '#shared/floating.util';
import { Toaster } from '#shared/components.util';
import { init } from '#shared/i18n';
onBeforeMount(() => {
Content.init();
Floating.init();
Toaster.init();
init()
const unmount = useRouter().afterEach((to, from, failure) => {
if(failure) return;
@@ -183,6 +185,10 @@ iconify-icon
@apply text-light-100 dark:text-dark-100;
}
.cm-focused
{
@apply outline-none;
}
.cm-editor .cm-content
{
@apply caret-light-100 dark:caret-dark-100;

View File

@@ -1,5 +1,6 @@
import { relations } from 'drizzle-orm';
import { relations, sql } from 'drizzle-orm';
import { int, text, sqliteTable as table, primaryKey, blob } from 'drizzle-orm/sqlite-core';
import type { ItemState } from '~/types/character';
export const usersTable = table("users", {
id: int().primaryKey({ autoIncrement: true }),
@@ -87,8 +88,8 @@ export const campaignTable = table("campaign", {
owner: int().notNull().references(() => usersTable.id, { onDelete: 'cascade', onUpdate: 'cascade' }),
link: text().notNull(),
status: text({ enum: ['PREPARING', 'PLAYING', 'ARCHIVED'] }).default('PREPARING'),
settings: text({ mode: 'json' }).default('{}'),
inventory: text({ mode: 'json' }).default('[]'),
settings: text({ mode: 'json' }).default({}).$type<{}>(),
items: text({ mode: 'json' }).default([]).$type<ItemState[]>(),
money: int().default(0),
public_notes: text().default(''),
dm_notes: text().default(''),
@@ -101,13 +102,6 @@ export const campaignCharactersTable = table("campaign_characters", {
id: int().references(() => campaignTable.id, { onDelete: 'cascade', onUpdate: 'cascade' }),
character: int().references(() => characterTable.id, { onDelete: 'cascade', onUpdate: 'cascade' }),
}, (table) => [primaryKey({ columns: [table.id, table.character] })]);
export const campaignLogsTable = table("campaign_logs", {
id: int().references(() => campaignTable.id, { onDelete: 'cascade', onUpdate: 'cascade' }),
target: int(),
timestamp: int({ mode: 'timestamp_ms' }).notNull(),
type: text({ enum: ['ITEM', 'CHARACTER', 'PLACE', 'EVENT', 'FIGHT', 'TEXT'] }),
details: text().notNull(),
}, (table) => [primaryKey({ columns: [table.id, table.target, table.timestamp] })]);
export const usersRelation = relations(usersTable, ({ one, many }) => ({
data: one(usersDataTable, { fields: [usersTable.id], references: [usersDataTable.id], }),
@@ -153,7 +147,6 @@ export const characterChoicesRelation = relations(characterChoicesTable, ({ one
export const campaignRelation = relations(campaignTable, ({ one, many }) => ({
members: many(campaignMembersTable),
characters: many(campaignCharactersTable),
logs: many(campaignLogsTable),
owner: one(usersTable, { fields: [campaignTable.owner], references: [usersTable.id], }),
}));
export const campaignMembersRelation = relations(campaignMembersTable, ({ one }) => ({
@@ -163,7 +156,4 @@ export const campaignMembersRelation = relations(campaignMembersTable, ({ one })
export const campaignCharacterRelation = relations(campaignCharactersTable, ({ one }) => ({
campaign: one(campaignTable, { fields: [campaignCharactersTable.id], references: [campaignTable.id], }),
character: one(characterTable, { fields: [campaignCharactersTable.character], references: [characterTable.id], }),
}));
export const campaignLogsRelation = relations(campaignLogsTable, ({ one }) => ({
campaign: one(campaignTable, { fields: [campaignLogsTable.id], references: [campaignTable.id], }),
}));

View File

@@ -98,7 +98,7 @@ onMounted(() => {
}, (item) => item.navigable);
(path.value?.split('/').map((e, i, a) => a.slice(0, i).join('/')) ?? []).forEach(e => tree?.toggle(tree.tree.search('path', e)[0], true));
treeParent.value!.replaceChildren(tree.container);
treeParent.value?.replaceChildren(tree.container);
})
}
})

View File

@@ -4,6 +4,7 @@ import { unifySlug } from '#shared/general.util';
definePageMeta({
guestsGoesTo: '/user/login',
validState: true,
});
const id = unifySlug(useRouter().currentRoute.value.params.id ?? "new");
const container = useTemplateRef('container');

View File

@@ -9,6 +9,8 @@ definePageMeta({
const { data: characters, error, status } = await useFetch(`/api/character`);
const config = characterConfig as CharacterConfig;
const { user } = useUserSession();
async function deleteCharacter(id: number)
{
status.value = "pending";
@@ -78,10 +80,11 @@ async function duplicateCharacter(id: number)
</div>
<div v-else class="flex flex-col gap-2 items-center flex-1">
<span class="text-lg font-bold">Vous n'avez pas encore de personnage</span>
<NuxtLink 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
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>
<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]
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

View File

@@ -3,6 +3,8 @@ import characterConfig from '#shared/character-config.json';
import type { CharacterConfig } from '~/types/character';
const { data: characters, error, status } = await useFetch(`/api/character`, { params: { visibility: "public" } });
const config = characterConfig as CharacterConfig;
const { user } = useUserSession();
</script>
<template>
@@ -34,11 +36,14 @@ const config = characterConfig as CharacterConfig;
</div>
<div v-else class="flex flex-col gap-2 items-center flex-1">
<span class="text-lg font-bold">Il n'existe pas encore de personnage public</span>
Soyez le premier à partager vos créations !
<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
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>
<template v-if="user && user.state === 1">
Soyez le premier à partager vos créations !
<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
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>
</template>
<div v-else>Veuillez valider votre adresse mail pour pouvoir créer des personnages.</div>
</div>
</template>
<div v-else>

View File

@@ -100,7 +100,7 @@ onMounted(async () => {
editor = new Editor();
Content.ready.then(() => tree.value!.replaceChild(editor.tree.container, load));
Content.ready.then(() => tree.value?.replaceChild(editor.tree.container, load));
container.value.appendChild(editor.container);
}
});

View File

@@ -4,7 +4,7 @@ import type { Serialize } from 'nitropack';
export type CampaignVariables = {
money: number;
inventory: ItemState[];
items: ItemState[];
};
export type Campaign = {
id: number;
@@ -16,11 +16,4 @@ export type Campaign = {
characters: Array<Partial<{ character: { id: number, name: string, owner: number } }>>;
public_notes: string;
dm_notes: string;
logs: CampaignLog[];
} & CampaignVariables;
export type CampaignLog = {
target: number;
timestamp: Serialize<Date>;
type: 'ITEM' | 'CHARACTER' | 'PLACE' | 'FIGHT' | 'TEXT';
details: string;
};
} & CampaignVariables;

View File

@@ -57,39 +57,54 @@ export type CharacterVariables = {
money: number;
};
type CommonState = {
capacity?: number;
powercost?: number;
};
type ArmorState = { health?: number };
type WeaponState = { attack?: number | string, hit?: number };
type WondrousState = { };
type MundaneState = { };
type ItemState = {
id: string;
amount: number;
enchantments?: string[];
charges?: number;
equipped?: boolean;
state?: any;
state?: (ArmorState | WeaponState | WondrousState | MundaneState) & CommonState;
};
export type CharacterConfig = {
peoples: Record<string, RaceConfig>;
training: Record<MainStat, Record<TrainingLevel, FeatureID[]>>;
spells: Record<string, SpellConfig>;
spells: Record<string, SpellConfig | ArtConfig>;
aspects: Record<string, AspectConfig>;
features: Record<FeatureID, Feature>;
enchantments: Record<string, EnchantementConfig>; //TODO
enchantments: Record<string, EnchantementConfig>;
items: Record<string, ItemConfig>;
sickness: Record<string, { id: string, name: string, description: string, effect: FeatureID[] }>;
action: Record<string, { id: string, name: string, description: string, cost: number }>;
reaction: Record<string, { id: string, name: string, description: string, cost: number }>;
freeaction: Record<string, { id: string, name: string, description: string }>;
passive: Record<string, { id: string, name: string, description: string }>;
texts: Record<i18nID, Localized>;
//Each of these groups extend an existing feature as they all use the same properties
sickness: Record<FeatureID, { stage: number }>; //TODO
poisons: Record<FeatureID, { difficulty: number, efficienty: number, solubility: number }>; //TODO
dedications: Record<FeatureID, { id: string, name: string, description: i18nID, effect: FeatureID[], requirement: Array<{ stat: MainStat, amount: number }> }>; //TODO
};
export type EnchantementConfig = {
id: string;
name: string; //TODO -> TextID
description: i18nID;
effect: Array<FeatureEquipment | FeatureValue | FeatureList>;
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.
}
export type ItemConfig = CommonItemConfig & (ArmorConfig | WeaponConfig | WondrousConfig | MundaneConfig);
type CommonItemConfig = {
id: string;
name: string; //TODO -> TextID
flavoring: i18nID;
flavoring?: i18nID;
description: i18nID;
rarity: 'common' | 'uncommon' | 'rare' | 'legendary';
weight?: number; //Optionnal but highly recommended
@@ -101,6 +116,7 @@ type CommonItemConfig = {
effects?: Array<FeatureValue | FeatureEquipment | FeatureList>;
equippable: boolean;
consummable: boolean;
craft?: { mineral: number, natural: number, processed: number, magical: number };
}
type ArmorConfig = {
category: 'armor';
@@ -126,7 +142,7 @@ export type SpellConfig = {
id: string;
name: string; //TODO -> TextID
rank: 1 | 2 | 3 | 4;
type: SpellType;
type: Exclude<SpellType, "arts">;
cost: number;
speed: "action" | "reaction" | number;
elements: Array<SpellElement>;
@@ -135,6 +151,15 @@ export type SpellConfig = {
range: 'personnal' | number;
tags?: string[];
};
export type ArtConfig = {
id: string;
name: string; //TODO -> TextID
rank: 1 | 2 | 3;
type: "arts";
difficulty: number;
description: string; //TODO -> TextID
tags?: string[];
};
export type RaceConfig = {
id: string;
name: string; //TODO -> TextID
@@ -204,16 +229,19 @@ export type CompiledCharacter = {
spellslots: number; //Max
artslots: number; //Max
spellranks: Record<SpellType, 0 | 1 | 2 | 3>;
aspect: string; //ID
aspect: {
id: string,
amount: number;
duration: number;
bonus: number;
tier: 0 | 1 | 2;
};
speed: number | false;
capacity: number | false;
initiative: number;
exhaust: number;
itempower: number;
action: number;
reaction: number;
variables: CharacterVariables,
defense: {
@@ -238,10 +266,18 @@ export type CompiledCharacter = {
};
bonus: {
defense: Partial<Record<MainStat, number>>;
defense: Partial<Record<MainStat, number>>; //Defense aux jets de resistance
abilities: Partial<Record<Ability, number>>;
spells: {
type: Partial<Record<SpellType, number>>;
rank: Partial<Record<1 | 2 | 3 | 4, number>>;
elements: Partial<Record<SpellElement, number>>;
};
weapon: Partial<Record<WeaponType, number>>;
}; //Any special bonus goes here
resistance: Record<string, number>;
resistance: Partial<Record<Resistance, number>>; //Bonus à l'attaque
craft: { level: number, bonus: number };
modifier: Record<MainStat, number>;
abilities: Partial<Record<Ability, number>>;

View File

@@ -17,5 +17,4 @@ type CanvasPreferences = {
export type Localized = {
fr_FR?: string;
en_US?: string;
default: string;
}