Ajust database schema to recent changes

This commit is contained in:
Clément Pons 2025-08-26 17:34:34 +02:00
parent da93fcd82d
commit 042d4479ee
17 changed files with 898 additions and 106 deletions

BIN
db.sqlite

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,5 +1,6 @@
import { relations } from 'drizzle-orm'; import { relations } from 'drizzle-orm';
import { int, text, sqliteTable as table, primaryKey, blob } from 'drizzle-orm/sqlite-core'; import { int, text, sqliteTable as table, primaryKey, blob } from 'drizzle-orm/sqlite-core';
import { ABILITIES, MAIN_STATS } from '~/shared/character.util';
export const usersTable = table("users", { export const usersTable = table("users", {
id: int().primaryKey({ autoIncrement: true }), id: int().primaryKey({ autoIncrement: true }),
@ -54,10 +55,9 @@ export const characterTable = table("character", {
owner: int().notNull().references(() => usersTable.id, { onDelete: 'cascade', onUpdate: 'cascade' }), owner: int().notNull().references(() => usersTable.id, { onDelete: 'cascade', onUpdate: 'cascade' }),
people: text().notNull(), people: text().notNull(),
level: int().notNull().default(1), level: int().notNull().default(1),
variables: text({ mode: 'json' }).notNull().default('{"health": 0,"mana": 0,"spells": [],"equipment": [],"exhaustion": 0,"sickness": []}'),
aspect: int(), aspect: int(),
notes: text(), notes: text(),
health: int().notNull().default(0),
mana: int().notNull().default(0),
visibility: text({ enum: ['private', 'public'] }).notNull().default('private'), visibility: text({ enum: ['private', 'public'] }).notNull().default('private'),
thumbnail: blob(), thumbnail: blob(),
@ -83,17 +83,6 @@ export const characterAbilitiesTable = table("character_abilities", {
max: int().notNull().default(0), max: int().notNull().default(0),
}, (table) => [primaryKey({ columns: [table.character, table.ability] })]); }, (table) => [primaryKey({ columns: [table.character, table.ability] })]);
export const characterModifiersTable = table("character_modifiers", {
character: int().notNull().references(() => characterTable.id, { onDelete: 'cascade', onUpdate: 'cascade' }),
modifier: text({ enum: ["strength","dexterity","constitution","intelligence","curiosity","charisma","psyche"] }).notNull(),
value: int().notNull().default(0),
}, (table) => [primaryKey({ columns: [table.character, table.modifier] })]);
export const characterSpellsTable = table("character_spell", {
character: int().notNull().references(() => characterTable.id, { onDelete: 'cascade', onUpdate: 'cascade' }),
value: text().notNull(),
}, (table) => [primaryKey({ columns: [table.character, table.value] })]);
export const characterChoicesTable = table("character_choices", { export const characterChoicesTable = table("character_choices", {
character: int().notNull().references(() => characterTable.id, { onDelete: 'cascade', onUpdate: 'cascade' }), character: int().notNull().references(() => characterTable.id, { onDelete: 'cascade', onUpdate: 'cascade' }),
id: text().notNull(), id: text().notNull(),
@ -123,8 +112,6 @@ export const characterRelation = relations(characterTable, ({ one, many }) => ({
training: many(characterTrainingTable), training: many(characterTrainingTable),
levels: many(characterLevelingTable), levels: many(characterLevelingTable),
abilities: many(characterAbilitiesTable), abilities: many(characterAbilitiesTable),
modifiers: many(characterModifiersTable),
spells: many(characterSpellsTable),
choices: many(characterChoicesTable) choices: many(characterChoicesTable)
})); }));
@ -137,12 +124,6 @@ export const characterLevelingRelation = relations(characterLevelingTable, ({ on
export const characterAbilitiesRelation = relations(characterAbilitiesTable, ({ one }) => ({ export const characterAbilitiesRelation = relations(characterAbilitiesTable, ({ one }) => ({
character: one(characterTable, { fields: [characterAbilitiesTable.character], references: [characterTable.id] }) character: one(characterTable, { fields: [characterAbilitiesTable.character], references: [characterTable.id] })
})); }));
export const characterModifierRelation = relations(characterModifiersTable, ({ one }) => ({
character: one(characterTable, { fields: [characterModifiersTable.character], references: [characterTable.id] })
}));
export const characterSpellsRelation = relations(characterSpellsTable, ({ one }) => ({
character: one(characterTable, { fields: [characterSpellsTable.character], references: [characterTable.id] })
}));
export const characterChoicesRelation = relations(characterChoicesTable, ({ one }) => ({ export const characterChoicesRelation = relations(characterChoicesTable, ({ one }) => ({
character: one(characterTable, { fields: [characterChoicesTable.character], references: [characterTable.id] }) character: one(characterTable, { fields: [characterChoicesTable.character], references: [characterTable.id] })
})); }));

View File

@ -0,0 +1,5 @@
DROP TABLE `character_modifiers`;--> statement-breakpoint
DROP TABLE `character_spell`;--> statement-breakpoint
ALTER TABLE `character` ADD `variables` text DEFAULT '{"health": 0,"mana": 0,"spells": [],"equipment": [],"exhaustion": 0,"sickness": []}' NOT NULL;--> statement-breakpoint
ALTER TABLE `character` DROP COLUMN `health`;--> statement-breakpoint
ALTER TABLE `character` DROP COLUMN `mana`;

View File

@ -0,0 +1,702 @@
{
"version": "6",
"dialect": "sqlite",
"id": "05b549e7-5b3f-40f4-9461-05db59391e20",
"prevId": "6651137c-a198-4538-86be-7cb8b88ca998",
"tables": {
"character_abilities": {
"name": "character_abilities",
"columns": {
"character": {
"name": "character",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"ability": {
"name": "ability",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"value": {
"name": "value",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": 0
},
"max": {
"name": "max",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": 0
}
},
"indexes": {},
"foreignKeys": {
"character_abilities_character_character_id_fk": {
"name": "character_abilities_character_character_id_fk",
"tableFrom": "character_abilities",
"tableTo": "character",
"columnsFrom": [
"character"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "cascade"
}
},
"compositePrimaryKeys": {
"character_abilities_character_ability_pk": {
"columns": [
"character",
"ability"
],
"name": "character_abilities_character_ability_pk"
}
},
"uniqueConstraints": {},
"checkConstraints": {}
},
"character_choices": {
"name": "character_choices",
"columns": {
"character": {
"name": "character",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"id": {
"name": "id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"choice": {
"name": "choice",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {
"character_choices_character_character_id_fk": {
"name": "character_choices_character_character_id_fk",
"tableFrom": "character_choices",
"tableTo": "character",
"columnsFrom": [
"character"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "cascade"
}
},
"compositePrimaryKeys": {
"character_choices_character_id_choice_pk": {
"columns": [
"character",
"id",
"choice"
],
"name": "character_choices_character_id_choice_pk"
}
},
"uniqueConstraints": {},
"checkConstraints": {}
},
"character_leveling": {
"name": "character_leveling",
"columns": {
"character": {
"name": "character",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"level": {
"name": "level",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"choice": {
"name": "choice",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {
"character_leveling_character_character_id_fk": {
"name": "character_leveling_character_character_id_fk",
"tableFrom": "character_leveling",
"tableTo": "character",
"columnsFrom": [
"character"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "cascade"
}
},
"compositePrimaryKeys": {
"character_leveling_character_level_pk": {
"columns": [
"character",
"level"
],
"name": "character_leveling_character_level_pk"
}
},
"uniqueConstraints": {},
"checkConstraints": {}
},
"character": {
"name": "character",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"autoincrement": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"owner": {
"name": "owner",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"people": {
"name": "people",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"level": {
"name": "level",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": 1
},
"variables": {
"name": "variables",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'{}'"
},
"aspect": {
"name": "aspect",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"notes": {
"name": "notes",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"visibility": {
"name": "visibility",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'private'"
},
"thumbnail": {
"name": "thumbnail",
"type": "blob",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {
"character_owner_users_id_fk": {
"name": "character_owner_users_id_fk",
"tableFrom": "character",
"tableTo": "users",
"columnsFrom": [
"owner"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "cascade"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"character_training": {
"name": "character_training",
"columns": {
"character": {
"name": "character",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"stat": {
"name": "stat",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"level": {
"name": "level",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"choice": {
"name": "choice",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {
"character_training_character_character_id_fk": {
"name": "character_training_character_character_id_fk",
"tableFrom": "character_training",
"tableTo": "character",
"columnsFrom": [
"character"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "cascade"
}
},
"compositePrimaryKeys": {
"character_training_character_stat_level_pk": {
"columns": [
"character",
"stat",
"level"
],
"name": "character_training_character_stat_level_pk"
}
},
"uniqueConstraints": {},
"checkConstraints": {}
},
"email_validation": {
"name": "email_validation",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"timestamp": {
"name": "timestamp",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"project_content": {
"name": "project_content",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"content": {
"name": "content",
"type": "blob",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"project_files": {
"name": "project_files",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"path": {
"name": "path",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"owner": {
"name": "owner",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"title": {
"name": "title",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"type": {
"name": "type",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"navigable": {
"name": "navigable",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": true
},
"private": {
"name": "private",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": false
},
"order": {
"name": "order",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"timestamp": {
"name": "timestamp",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"project_files_path_unique": {
"name": "project_files_path_unique",
"columns": [
"path"
],
"isUnique": true
}
},
"foreignKeys": {
"project_files_owner_users_id_fk": {
"name": "project_files_owner_users_id_fk",
"tableFrom": "project_files",
"tableTo": "users",
"columnsFrom": [
"owner"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "cascade"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"user_permissions": {
"name": "user_permissions",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"permission": {
"name": "permission",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {
"user_permissions_id_users_id_fk": {
"name": "user_permissions_id_users_id_fk",
"tableFrom": "user_permissions",
"tableTo": "users",
"columnsFrom": [
"id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "cascade"
}
},
"compositePrimaryKeys": {
"user_permissions_id_permission_pk": {
"columns": [
"id",
"permission"
],
"name": "user_permissions_id_permission_pk"
}
},
"uniqueConstraints": {},
"checkConstraints": {}
},
"user_sessions": {
"name": "user_sessions",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"user_id": {
"name": "user_id",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"timestamp": {
"name": "timestamp",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {
"user_sessions_user_id_users_id_fk": {
"name": "user_sessions_user_id_users_id_fk",
"tableFrom": "user_sessions",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "cascade"
}
},
"compositePrimaryKeys": {
"user_sessions_id_user_id_pk": {
"columns": [
"id",
"user_id"
],
"name": "user_sessions_id_user_id_pk"
}
},
"uniqueConstraints": {},
"checkConstraints": {}
},
"users_data": {
"name": "users_data",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"signin": {
"name": "signin",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"lastTimestamp": {
"name": "lastTimestamp",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {
"users_data_id_users_id_fk": {
"name": "users_data_id_users_id_fk",
"tableFrom": "users_data",
"tableTo": "users",
"columnsFrom": [
"id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "cascade"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"users": {
"name": "users",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"autoincrement": true
},
"username": {
"name": "username",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"hash": {
"name": "hash",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"state": {
"name": "state",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": 0
}
},
"indexes": {
"users_username_unique": {
"name": "users_username_unique",
"columns": [
"username"
],
"isUnique": true
},
"users_email_unique": {
"name": "users_email_unique",
"columns": [
"email"
],
"isUnique": true
},
"users_hash_unique": {
"name": "users_hash_unique",
"columns": [
"hash"
],
"isUnique": true
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
}
},
"views": {},
"enums": {},
"_meta": {
"schemas": {},
"tables": {},
"columns": {}
},
"internal": {
"indexes": {}
}
}

View File

@ -113,6 +113,13 @@
"when": 1756214160038, "when": 1756214160038,
"tag": "0015_typical_blade", "tag": "0015_typical_blade",
"breakpoints": true "breakpoints": true
},
{
"idx": 16,
"version": "6",
"when": 1756221197092,
"tag": "0016_wild_the_anarchist",
"breakpoints": true
} }
] ]
} }

View File

@ -13,7 +13,6 @@ const config = characterConfig as CharacterConfig;
const id = useRouter().currentRoute.value.params.id; const id = useRouter().currentRoute.value.params.id;
const { user } = useUserSession(); const { user } = useUserSession();
const { add } = useToast();
const { data, status, error } = await useFetch(`/api/character/${id}`); const { data, status, error } = await useFetch(`/api/character/${id}`);
const compiler = new CharacterCompiler(data.value ?? defaultCharacter); const compiler = new CharacterCompiler(data.value ?? defaultCharacter);
@ -52,7 +51,7 @@ text-light-purple dark:text-dark-purple border-light-purple dark:border-dark-pur
</div> </div>
<div class="flex flex-col"> <div class="flex flex-col">
<span class="font-bold">Niveau {{ character.level }}</span> <span class="font-bold">Niveau {{ character.level }}</span>
<span>{{ character.race === -1 ? "Race inconnue" : config.peoples[character.race]!.name }}</span> <span>{{ config.peoples[character.race]?.name ?? 'Peuple inconnu' }}</span>
</div> </div>
</div> </div>
<div class="flex flex-col lg:border-l border-light-30 dark:border-dark-30 py-4 ps-4"> <div class="flex flex-col lg:border-l border-light-30 dark:border-dark-30 py-4 ps-4">
@ -132,7 +131,7 @@ text-light-purple dark:text-dark-purple border-light-purple dark:border-dark-pur
<TabsTrigger value="notes" class="px-2 py-1 border-b border-transparent hover:border-accent-blue">Notes</TabsTrigger> <TabsTrigger value="notes" class="px-2 py-1 border-b border-transparent hover:border-accent-blue">Notes</TabsTrigger>
</TabsList> </TabsList>
<TabsContent value="features"> <TabsContent value="features">
<div class="flex flex-1 flex-col ps-8 gap-4 py-8"> <div class="flex flex-1 flex-col ps-8 gap-4 py-4">
<div class="grid grid-cols-3 gap-4"> <div class="grid grid-cols-3 gap-4">
<div class="flex flex-col"> <div class="flex flex-col">
<span class="text-lg font-semibold">Actions</span> <span class="text-lg font-semibold">Actions</span>
@ -157,9 +156,10 @@ text-light-purple dark:text-dark-purple border-light-purple dark:border-dark-pur
</div> </div>
</TabsContent> </TabsContent>
<TabsContent v-if="character.spellslots > 0" value="spells"> <TabsContent v-if="character.spellslots > 0" value="spells">
<div class="flex flex-1 flex-col ps-8 gap-4 py-8"> <div class="flex flex-1 flex-col ps-8 gap-4 py-2">
<div class="flex flex-col" v-if="character.lists.spells && character.lists.spells.length > 0"> <div class="flex flex-1 justify-between items-center"><span class="italic text-light-70 dark:text-dark-70 text-sm">{{ character.variables.spells.length }} / {{ character.spellslots }} sorts maitrisés</span><Button icon><Icon icon="radix-icons:plus" class="w-6 h-6"/></Button></div>
<div class="pb-4 px-2 mt-4 border-b last:border-none border-light-30 dark:border-dark-30 flex flex-col" v-for="spell of character.lists.spells.map(e => config.spells.find((f: SpellConfig) => f.id === e)).filter(e => !!e)"> <div class="flex flex-col" v-if="[...(character.lists.spells ?? []), ...character.variables.spells].length > 0">
<div class="pb-4 px-2 mt-4 border-b last:border-none border-light-30 dark:border-dark-30 flex flex-col" v-for="spell of [...(character.lists.spells ?? []), ...character.variables.spells].map(e => config.spells.find((f: SpellConfig) => f.id === e)).filter(e => !!e)">
<div class="flex flex-row justify-between"> <div class="flex flex-row justify-between">
<span class="text-lg font-bold">{{ spell.name }}</span> <span class="text-lg font-bold">{{ spell.name }}</span>
<div class="flex flex-row items-center gap-6"> <div class="flex flex-row items-center gap-6">
@ -170,7 +170,7 @@ text-light-purple dark:text-dark-purple border-light-purple dark:border-dark-pur
<span class="">Rang {{ spell.rank }}</span><span>/</span> <span class="">Rang {{ spell.rank }}</span><span>/</span>
<span class="">{{ spellTypeTexts[spell.type] }}</span><span>/</span> <span class="">{{ spellTypeTexts[spell.type] }}</span><span>/</span>
<span class="">{{ spell.cost }} mana</span><span>/</span> <span class="">{{ spell.cost }} mana</span><span>/</span>
<span class="">{{ typeof spell.speed === 'string' ? spell.speed : `${spell.speed} minutes` }}</span> <span class="capitalize">{{ typeof spell.speed === 'string' ? spell.speed : `${spell.speed} minutes` }}</span>
</div> </div>
</div> </div>
</div> </div>

View File

@ -3,7 +3,7 @@ import useDatabase from '~/composables/useDatabase';
import { characterTable, userPermissionsTable } from '~/db/schema'; import { characterTable, userPermissionsTable } from '~/db/schema';
import { hasPermissions } from '~/shared/auth.util'; import { hasPermissions } from '~/shared/auth.util';
import { group } from '~/shared/general.util'; import { group } from '~/shared/general.util';
import type { Character, DoubleIndex, Level, MainStat, TrainingLevel } from '~/types/character'; import type { Character, Level, MainStat, TrainingLevel } from '~/types/character';
export default defineEventHandler(async (e) => { export default defineEventHandler(async (e) => {
let { visibility } = getQuery(e) as { visibility?: "public" | "own" | "admin" }; let { visibility } = getQuery(e) as { visibility?: "public" | "own" | "admin" };
@ -55,8 +55,6 @@ export default defineEventHandler(async (e) => {
with: { with: {
abilities: true, abilities: true,
levels: true, levels: true,
modifiers: true,
spells: true,
training: true, training: true,
choices: true, choices: true,
user: { user: {
@ -76,14 +74,11 @@ export default defineEventHandler(async (e) => {
level: character.level, level: character.level,
aspect: character.aspect, aspect: character.aspect,
notes: character.notes, notes: character.notes,
health: character.health, variables: character.variables,
mana: character.mana,
training: character.training.reduce((p, v) => { if(!(v.stat in p)) p[v.stat] = []; p[v.stat].push([v.level as TrainingLevel, v.choice]); return p; }, {} as Record<MainStat, DoubleIndex<TrainingLevel>[]>), training: character.training.reduce((p, v) => { p[v.stat] ??= {}; p[v.stat][v.level as TrainingLevel] = v.choice; return p; }, {} as Record<MainStat, Partial<Record<TrainingLevel, number>>>),
leveling: character.levels.map(e => [e.level as Level, e.choice] as DoubleIndex<Level>), leveling: group(character.levels, "level", "choice"),
abilities: group(character.abilities, "ability", "value"), abilities: group(character.abilities, "ability", "value"),
spells: character.spells.map(e => e.value),
modifiers: group(character.modifiers, "modifier", "value"),
choices: character.choices.reduce((p, v) => { p[v.id] ??= []; p[v.id]?.push(v.choice); return p; }, {} as Record<string, number[]>), choices: character.choices.reduce((p, v) => { p[v.id] ??= []; p[v.id]?.push(v.choice); return p; }, {} as Record<string, number[]>),
owner: character.owner, owner: character.owner,

View File

@ -1,6 +1,6 @@
import { z } from 'zod/v4'; import { z } from 'zod/v4';
import useDatabase from '~/composables/useDatabase'; import useDatabase from '~/composables/useDatabase';
import { characterAbilitiesTable, characterLevelingTable, characterModifiersTable, characterSpellsTable, characterTable, characterTrainingTable } from '~/db/schema'; import { characterAbilitiesTable, characterLevelingTable, characterTable, characterTrainingTable } from '~/db/schema';
import { CharacterValidation } from '#shared/character.util'; import { CharacterValidation } from '#shared/character.util';
import { type Ability, type MainStat } from '~/types/character'; import { type Ability, type MainStat } from '~/types/character';
@ -32,8 +32,7 @@ export default defineEventHandler(async (e) => {
level: body.data.level, level: body.data.level,
aspect: body.data.aspect, aspect: body.data.aspect,
notes: body.data.notes, notes: body.data.notes,
health: body.data.health, variables: body.data.variables,
mana: body.data.mana,
visibility: body.data.visibility, visibility: body.data.visibility,
thumbnail: body.data.thumbnail, thumbnail: body.data.thumbnail,
}).returning({ id: characterTable.id }).get().id; }).returning({ id: characterTable.id }).get().id;
@ -43,8 +42,6 @@ export default defineEventHandler(async (e) => {
const training = Object.entries(body.data.training).flatMap(e => Object.entries(e[1]).map(_e => ({ character: id, stat: e[0] as MainStat, level: parseInt(_e[0], 10), choice: _e[1]! }))); const training = Object.entries(body.data.training).flatMap(e => Object.entries(e[1]).map(_e => ({ character: id, stat: e[0] as MainStat, level: parseInt(_e[0], 10), choice: _e[1]! })));
if(training.length > 0) tx.insert(characterTrainingTable).values(training).run(); if(training.length > 0) tx.insert(characterTrainingTable).values(training).run();
if(body.data.spells.length > 0) tx.insert(characterSpellsTable).values(body.data.spells.map(e => ({ character: id, value: e }))).run();
const abilities = Object.entries(body.data.abilities).map(e => ({ character: id, ability: e[0] as Ability, value: e[1] })); const abilities = Object.entries(body.data.abilities).map(e => ({ character: id, ability: e[0] as Ability, value: e[1] }));
if(abilities.length > 0) tx.insert(characterAbilitiesTable).values(abilities).run(); if(abilities.length > 0) tx.insert(characterAbilitiesTable).values(abilities).run();

View File

@ -1,8 +1,7 @@
import { and, eq, sql } from 'drizzle-orm';
import useDatabase from '~/composables/useDatabase'; import useDatabase from '~/composables/useDatabase';
import { characterTable } from '~/db/schema'; import { characterTable } from '~/db/schema';
import { group } from '~/shared/general.util'; import { group } from '~/shared/general.util';
import type { Character, DoubleIndex, Level, MainStat, TrainingLevel } from '~/types/character'; import type { Character, CharacterVariables, Level, MainStat, TrainingLevel } from '~/types/character';
export default defineEventHandler(async (e) => { export default defineEventHandler(async (e) => {
const id = getRouterParam(e, "id"); const id = getRouterParam(e, "id");
@ -26,9 +25,8 @@ export default defineEventHandler(async (e) => {
with: { with: {
abilities: true, abilities: true,
levels: true, levels: true,
modifiers: true,
spells: true,
training: true, training: true,
choices: true,
user: { user: {
columns: { username: true } columns: { username: true }
} }
@ -46,15 +44,12 @@ export default defineEventHandler(async (e) => {
level: character.level, level: character.level,
aspect: character.aspect, aspect: character.aspect,
notes: character.notes, notes: character.notes,
health: character.health, variables: character.variables,
mana: character.mana,
training: character.training.reduce((p, v) => { p[v.stat] ??= {}; p[v.stat][v.level as TrainingLevel] = v.choice; return p; }, {} as Record<MainStat, Partial<Record<TrainingLevel, number>>>), training: character.training.reduce((p, v) => { p[v.stat] ??= {}; p[v.stat][v.level as TrainingLevel] = v.choice; return p; }, {} as Record<MainStat, Partial<Record<TrainingLevel, number>>>),
leveling: character.levels.reduce((p, v) => { p[v.level as Level] = v.choice; return p; }, {} as Partial<Record<Level, number>>), leveling: group(character.levels, "level", "choice"),
abilities: group(character.abilities.map(e => ({ ...e, value: e.value })), "ability", "value"), abilities: group(character.abilities, "ability", "value"),
spells: character.spells.map(e => e.value), choices: character.choices.reduce((p, v) => { p[v.id] ??= []; p[v.id]?.push(v.choice); return p; }, {} as Record<string, number[]>),
modifiers: group(character.modifiers, "modifier", "value"),
choices: {},
owner: character.owner, owner: character.owner,
username: character.user.username, username: character.user.username,

View File

@ -1,6 +1,6 @@
import { eq } from 'drizzle-orm'; import { eq } from 'drizzle-orm';
import useDatabase from '~/composables/useDatabase'; import useDatabase from '~/composables/useDatabase';
import { characterAbilitiesTable, characterChoicesTable, characterLevelingTable, characterModifiersTable, characterSpellsTable, characterTable, characterTrainingTable } from '~/db/schema'; import { characterAbilitiesTable, characterChoicesTable, characterLevelingTable, characterTable, characterTrainingTable } from '~/db/schema';
import { CharacterValidation } from '#shared/character.util'; import { CharacterValidation } from '#shared/character.util';
import { type Ability, type MainStat } from '~/types/character'; import { type Ability, type MainStat } from '~/types/character';
@ -43,16 +43,13 @@ export default defineEventHandler(async (e) => {
level: body.data.level, level: body.data.level,
aspect: body.data.aspect, aspect: body.data.aspect,
notes: body.data.notes, notes: body.data.notes,
health: body.data.health, variables: body.data.variables,
mana: body.data.mana,
visibility: body.data.visibility, visibility: body.data.visibility,
thumbnail: body.data.thumbnail, thumbnail: body.data.thumbnail,
}).where(eq(characterTable.id, id)).run(); }).where(eq(characterTable.id, id)).run();
tx.delete(characterLevelingTable).where(eq(characterLevelingTable.character, id)).run(); tx.delete(characterLevelingTable).where(eq(characterLevelingTable.character, id)).run();
tx.delete(characterTrainingTable).where(eq(characterTrainingTable.character, id)).run(); tx.delete(characterTrainingTable).where(eq(characterTrainingTable.character, id)).run();
tx.delete(characterModifiersTable).where(eq(characterModifiersTable.character, id)).run();
tx.delete(characterSpellsTable).where(eq(characterSpellsTable.character, id)).run();
tx.delete(characterAbilitiesTable).where(eq(characterAbilitiesTable.character, id)).run(); tx.delete(characterAbilitiesTable).where(eq(characterAbilitiesTable.character, id)).run();
tx.delete(characterChoicesTable).where(eq(characterChoicesTable.character, id)).run(); tx.delete(characterChoicesTable).where(eq(characterChoicesTable.character, id)).run();
@ -62,11 +59,6 @@ export default defineEventHandler(async (e) => {
const training = Object.entries(body.data.training).flatMap(e => Object.entries(e[1]).filter(_e => _e[1] !== undefined).map(_e => ({ character: id, stat: e[0] as MainStat, level: parseInt(_e[0]), choice: _e[1]! }))); const training = Object.entries(body.data.training).flatMap(e => Object.entries(e[1]).filter(_e => _e[1] !== undefined).map(_e => ({ character: id, stat: e[0] as MainStat, level: parseInt(_e[0]), choice: _e[1]! })));
if(training.length > 0) tx.insert(characterTrainingTable).values(training).run(); if(training.length > 0) tx.insert(characterTrainingTable).values(training).run();
const modifiers = Object.entries(body.data.modifiers).filter(e => e[1] !== undefined).map((e) => ({ character: id, modifier: e[0] as MainStat, value: e[1] }));
if(modifiers.length > 0) tx.insert(characterModifiersTable).values(modifiers).run();
if(body.data.spells.length > 0) tx.insert(characterSpellsTable).values(body.data.spells.map(e => ({ character: id, value: e }))).run();
const abilities = Object.entries(body.data.abilities).filter(e => e[1] !== undefined).map(e => ({ character: id, ability: e[0] as Ability, value: e[1], max: 0 })); const abilities = Object.entries(body.data.abilities).filter(e => e[1] !== undefined).map(e => ({ character: id, ability: e[0] as Ability, value: e[1], max: 0 }));
if(abilities.length > 0) tx.insert(characterAbilitiesTable).values(abilities).run(); if(abilities.length > 0) tx.insert(characterAbilitiesTable).values(abilities).run();
}); });

View File

@ -1,6 +1,6 @@
import { eq } from 'drizzle-orm'; import { eq } from 'drizzle-orm';
import useDatabase from '~/composables/useDatabase'; import useDatabase from '~/composables/useDatabase';
import { characterAbilitiesTable, characterLevelingTable, characterModifiersTable, characterSpellsTable, characterTable, characterTrainingTable } from '~/db/schema'; import { characterAbilitiesTable, characterLevelingTable, characterTable, characterTrainingTable } from '~/db/schema';
export default defineEventHandler(async (e) => { export default defineEventHandler(async (e) => {
const id = getRouterParam(e, "id"); const id = getRouterParam(e, "id");
@ -36,8 +36,7 @@ export default defineEventHandler(async (e) => {
level: old.level, level: old.level,
aspect: old.aspect, aspect: old.aspect,
notes: old.notes, notes: old.notes,
health: old.health, variables: old.variables,
mana: old.mana,
visibility: old.visibility, visibility: old.visibility,
thumbnail: old.thumbnail, thumbnail: old.thumbnail,
}).returning({ id: characterTable.id }).get().id; }).returning({ id: characterTable.id }).get().id;
@ -48,12 +47,6 @@ export default defineEventHandler(async (e) => {
const training = tx.select().from(characterTrainingTable).where(eq(characterTrainingTable.character, parseInt(id, 10))).all(); const training = tx.select().from(characterTrainingTable).where(eq(characterTrainingTable.character, parseInt(id, 10))).all();
if(training.length > 0) tx.insert(characterTrainingTable).values(training.map(e => ({ character: _id, stat: e.stat, level: e.level, choice: e.choice }))).run(); if(training.length > 0) tx.insert(characterTrainingTable).values(training.map(e => ({ character: _id, stat: e.stat, level: e.level, choice: e.choice }))).run();
const modifiers = tx.select().from(characterModifiersTable).where(eq(characterModifiersTable.character, parseInt(id, 10))).all();
if(modifiers.length > 0) tx.insert(characterModifiersTable).values(modifiers.map(e => ({ character: _id, modifier: e.modifier, value: e.value }))).run();
const spells = tx.select().from(characterSpellsTable).where(eq(characterSpellsTable.character, parseInt(id, 10))).all();
if(spells.length > 0) tx.insert(characterSpellsTable).values(spells.map(e => ({ character: _id, value: e.value }))).run();
const abilities = tx.select().from(characterAbilitiesTable).where(eq(characterAbilitiesTable.character, parseInt(id, 10))).all(); const abilities = tx.select().from(characterAbilitiesTable).where(eq(characterAbilitiesTable.character, parseInt(id, 10))).all();
if(abilities.length > 0) tx.insert(characterAbilitiesTable).values(abilities.map(e => ({ character: _id, ability: e.ability, value: e.value, max: e.max }))).run(); if(abilities.length > 0) tx.insert(characterAbilitiesTable).values(abilities.map(e => ({ character: _id, ability: e.ability, value: e.value, max: e.max }))).run();

View File

@ -1,7 +1,7 @@
import { and, eq, sql } from 'drizzle-orm'; import { and, eq, sql } from 'drizzle-orm';
import useDatabase from '~/composables/useDatabase'; import useDatabase from '~/composables/useDatabase';
import { characterTable } from '~/db/schema'; import { characterTable } from '~/db/schema';
import type { Character, CharacterValues } from '~/types/character'; import type { CharacterVariables } from '~/types/character';
export default defineEventHandler(async (e) => { export default defineEventHandler(async (e) => {
const id = getRouterParam(e, "id"); const id = getRouterParam(e, "id");
@ -27,7 +27,7 @@ export default defineEventHandler(async (e) => {
if(character !== undefined) if(character !== undefined)
{ {
return character as CharacterValues; return character as CharacterVariables;
} }
setResponseStatus(e, 404); setResponseStatus(e, 404);

View File

@ -2675,6 +2675,89 @@
"damage" "damage"
], ],
"effect": "Place une anomalie visuelle à 3 cases émettant une [[6. Visibilité et lumière#Lumière intense|lumière vive]] à 9 cases. Lorsqu'un être vivant rentre en contact avec l'anomalie, il absorbe toute l'énergie magique et subit 4d8 points de dégâts magique" "effect": "Place une anomalie visuelle à 3 cases émettant une [[6. Visibilité et lumière#Lumière intense|lumière vive]] à 9 cases. Lorsqu'un être vivant rentre en contact avec l'anomalie, il absorbe toute l'énergie magique et subit 4d8 points de dégâts magique"
},
{
"id": "9jq3pkj7sgfgq6q4ovwoanig6ha8g2ic",
"name": "Dévastation élémentaire",
"rank": 1,
"type": "precision",
"cost": 8,
"speed": "action",
"elements": [
"fire",
"ice",
"thunder"
],
"effect": "Faites un jet d'attaque soit la [[1. Entrainement#Dextérité|dextérité]], soit l'[[1. Entrainement#L'intelligence|intelligence]], soit la [[1. Entrainement#La psyché|psyché]]. Inflige 10+3d10 dégâts. Si vous attaquez avec la dextérité, vous infligez des dégâts de feu. Si vous attaquez avec l'intelligence, vous infligez des dégâts de glace et si vous attaquez avec la psyché, vous faites des dégâts de foudre.",
"concentration": false,
"tags": [
"damage",
"debuff"
]
},
{
"id": "zltvtru98sm0ad9whiw5tty0gy4q2jur",
"name": "Soin",
"rank": 4,
"type": "precision",
"cost": 8,
"speed": "action",
"elements": [
"nature"
],
"effect": "Soigne 10+1d10 PV et guérit l'[[2. Liste des effets#Hébètement|hébètement]], le [[2. Liste des effets#Le saignement|saignement]] et les [[2. Liste des effets#L'empoisonnement|poisons]]. En soignant un personnage agonisant, vous pouvez choisir à la place de le stabiliser et de le ramener à 0 PV.",
"concentration": false,
"tags": [
"support"
]
},
{
"id": "bgm3c8vd2xqcwpcul1cakab89p5bfu4t",
"name": "Contresort",
"rank": 4,
"type": "knowledge",
"cost": 4,
"speed": "reaction",
"elements": [
"arcana"
],
"effect": "Perturbe les flux magique pour interrompre une canalisation en cours que vous voyez à portée. Le lanceur de sort doit faire un jet d'attaque avec l'[[1. Entrainement#L'intelligence|intelligence]] maintenir sa canalisation. Vous pouvez augmenter le coût du sort pour augmenter les chances de réussite. La difficulté est égale à 6 - le cout du sort à interrompre + le cout du contresort.",
"concentration": false,
"tags": [
"debuff"
]
},
{
"id": "kd84l3gujh4evsyriti4g9sk1zwbxu8d",
"name": "Focalisation destructrice",
"rank": 4,
"type": "knowledge",
"cost": 12,
"speed": "action",
"elements": [
"arcana"
],
"effect": "Vous focalisez les énergies magiques sur vous, rendant l'utilisation de sort plus complexe pour les autres durant 1 minute. La densité d'énergie anormale vous fait subir 5 points de dégâts par tour. Toute personne à 18 cases de vous subit un malus de -4 pour se [[1. Aspect#Transformations|transformer]], à ces jets d'attaques de sort et à ces difficulté de jet de résistance de sort.",
"concentration": true,
"tags": [
"debuff"
]
},
{
"id": "wj2rxkbw85zd9st8k2w3eezqc1naoy5g",
"name": "Domination mentale",
"rank": 4,
"type": "instinct",
"cost": 8,
"speed": "action",
"elements": [
"psyche"
],
"effect": "La cible touchée doit réussir un [[3. Résistance aux chocs#Le jet de résistance|jet de résistance]] (d12/7 + mod. d'[[1. Entrainement#L'intelligence|intelligence]]) de [[1. Entrainement#La psyché|psyché]] ou est [[2. Liste des effets#Possédé|possédé]].",
"concentration": true,
"tags": [
"debuff"
]
} }
], ],
"aspects": [ "aspects": [

View File

@ -25,15 +25,19 @@ export const defaultCharacter: Character = {
name: "", name: "",
people: undefined, people: undefined,
level: 1, level: 1,
health: 0,
mana: 0,
training: MAIN_STATS.reduce((p, v) => { p[v] = { 0: 0 }; return p; }, {} as Record<MainStat, Partial<Record<TrainingLevel, number>>>), training: MAIN_STATS.reduce((p, v) => { p[v] = { 0: 0 }; return p; }, {} as Record<MainStat, Partial<Record<TrainingLevel, number>>>),
leveling: { 1: 0 }, leveling: { 1: 0 },
abilities: {}, abilities: {},
spells: [],
modifiers: {},
choices: {}, choices: {},
variables: {
health: 0,
mana: 0,
spells: [],
equipment: [],
exhaustion: 0,
sickness: [],
},
owner: -1, owner: -1,
visibility: "private", visibility: "private",
@ -48,13 +52,7 @@ const defaultCompiledCharacter: (character: Character) => CompiledCharacter = (c
race: character.people!, race: character.people!,
modifier: MAIN_STATS.reduce((p, v) => { p[v] = 0; return p; }, {} as Record<MainStat, number>), modifier: MAIN_STATS.reduce((p, v) => { p[v] = 0; return p; }, {} as Record<MainStat, number>),
level: character.level, level: character.level,
variables: { variables: character.variables,
health: character.health,
mana: character.mana,
equipment: [],
exhaustion: 0,
sickness: [],
},
action: 0, action: 0,
reaction: 0, reaction: 0,
exhaust: 0, exhaust: 0,
@ -121,7 +119,7 @@ const defaultCompiledCharacter: (character: Character) => CompiledCharacter = (c
freeaction: [], freeaction: [],
reaction: [], reaction: [],
passive: [], passive: [],
spells: character.spells, spells: character.variables.spells,
}, },
aspect: "", aspect: "",
notes: character.notes ?? "", notes: character.notes ?? "",
@ -174,17 +172,26 @@ export const spellTypeTexts: Record<SpellType, string> = { "instinct": "Instinct
export const CharacterValidation = z.object({ export const CharacterValidation = z.object({
id: z.number(), id: z.number(),
name: z.string(), name: z.string(),
people: z.number().nullable(), people: z.string().nullable(),
level: z.number().min(1).max(20), level: z.number().min(1).max(20),
aspect: z.number().nullable().optional(), aspect: z.number().nullable().optional(),
notes: z.string().nullable().optional(), notes: z.string().nullable().optional(),
health: z.number().default(0),
mana: z.number().default(0),
training: z.record(z.enum(MAIN_STATS), z.record(z.enum(TRAINING_LEVELS.map(String)), z.number())), training: z.record(z.enum(MAIN_STATS), z.record(z.enum(TRAINING_LEVELS.map(String)), z.number())),
leveling: z.record(z.enum(LEVELS.map(String)), z.number()), leveling: z.record(z.enum(LEVELS.map(String)), z.number()),
abilities: z.record(z.enum(ABILITIES), z.number()), abilities: z.record(z.enum(ABILITIES), z.number()),
spells: z.string().array(),
choices: z.record(z.string(), z.array(z.number())), choices: z.record(z.string(), z.array(z.number())),
variables: z.object({
health: z.number(),
mana: z.number(),
exhaustion: z.number(),
sickness: z.array(z.object({
id: z.string(),
state: z.number().min(1).max(7).or(z.literal(true)),
})),
spells: z.array(z.string()),
equipment: z.array(z.string()),
}),
owner: z.number(), owner: z.number(),
username: z.string().optional(), username: z.string().optional(),
visibility: z.enum(["public", "private"]), visibility: z.enum(["public", "private"]),
@ -332,15 +339,17 @@ export class CharacterCompiler
protected compile(properties: string[]) protected compile(properties: string[])
{ {
const queue = properties; const queue = properties;
queue.forEach(property => { for(let i = 0; i < queue.length; i++)
{
const property = queue[i]!;
const buffer = this._buffer[property]; const buffer = this._buffer[property];
if(property === "") if(property === "")
return continue
if(buffer && buffer._dirty === true) if(buffer && buffer._dirty === true)
{ {
let sum = 0; let sum = 0, shortcut = false;
for(let i = 0; i < buffer.list.length; i++) for(let i = 0; i < buffer.list.length; i++)
{ {
if(typeof buffer.list[i]!.value === 'string') // Add or set a modifier if(typeof buffer.list[i]!.value === 'string') // Add or set a modifier
@ -349,13 +358,16 @@ export class CharacterCompiler
if(!modifier) if(!modifier)
{ {
queue.push(property); queue.push(property);
return; shortcut = true;
break;
} }
else if(modifier._dirty) else if(modifier._dirty)
{ {
//Put it back in queue since its dependencies haven't been resolved yet //Put it back in queue since its dependencies haven't been resolved yet
queue.push(buffer.list[i]!.value as string);
queue.push(property); queue.push(property);
return; shortcut = true;
break;
} }
else else
{ {
@ -374,6 +386,9 @@ export class CharacterCompiler
} }
} }
if(shortcut === true)
continue;
const path = property.split("/"); const path = property.split("/");
const object = path.length === 1 ? this._result : path.slice(0, -1).reduce((p, v) => { p[v] ??= {}; return p[v]; }, this._result as any); const object = path.length === 1 ? this._result : path.slice(0, -1).reduce((p, v) => { p[v] ??= {}; return p[v]; }, this._result as any);
@ -383,7 +398,7 @@ export class CharacterCompiler
this._buffer[property]!.value = sum; this._buffer[property]!.value = sum;
this._buffer[property]!._dirty = false; this._buffer[property]!._dirty = false;
} }
}); }
} }
} }
export class CharacterBuilder extends CharacterCompiler export class CharacterBuilder extends CharacterCompiler

47
types/character.d.ts vendored
View File

@ -10,8 +10,11 @@ export type SpellElement = typeof SPELL_ELEMENTS[number];
export type Alignment = typeof ALIGNMENTS[number]; export type Alignment = typeof ALIGNMENTS[number];
export type FeatureID = string; export type FeatureID = string;
export type TextID = string;
export type Resistance = string; export type Resistance = string;
export type Dice = `${number}d${4 | 6 | 8 | 10 | 12 | 20}`;
export type Character = { export type Character = {
id: number; id: number;
@ -20,16 +23,12 @@ export type Character = {
level: number; level: number;
aspect?: number; aspect?: number;
notes?: string | null; notes?: string | null;
health: number;
mana: number;
training: Record<MainStat, Partial<Record<TrainingLevel, number>>>; training: Record<MainStat, Partial<Record<TrainingLevel, number>>>;
leveling: Partial<Record<Level, number>>; leveling: Partial<Record<Level, number>>;
abilities: Partial<Record<Ability, number>>; //First is the ability, second is the max increment abilities: Partial<Record<Ability, number>>;
spells: string[]; //Spell ID variables: CharacterVariables;
modifiers: Partial<Record<MainStat, number>>; choices: Record<FeatureID, number[]>;
choices: Record<string, number[]>;
owner: number; owner: number;
username?: string; username?: string;
@ -40,8 +39,9 @@ export type CharacterVariables = {
mana: number; mana: number;
exhaustion: number; exhaustion: number;
sickness: Array<{ id: string, progress: number | true }>; sickness: Array<{ id: string, state: number | true }>;
equipment: Array<string>; spells: string[]; //Spell ID
equipment: string[]; //Equipment ID
}; };
export type CharacterConfig = { export type CharacterConfig = {
peoples: Record<string, RaceConfig>; peoples: Record<string, RaceConfig>;
@ -51,8 +51,35 @@ export type CharacterConfig = {
spells: SpellConfig[]; spells: SpellConfig[];
aspects: AspectConfig[]; aspects: AspectConfig[];
features: Record<FeatureID, Feature>; features: Record<FeatureID, Feature>;
enchantments: Record<string, { name: string, effect: FeatureEffect[] }>; //TODO
items: Record<string, ItemConfig & { enchantments: string[] }>;
lists: Record<string, { id: string, name: string, [key: string]: any }[]>; lists: Record<string, { id: string, name: string, [key: string]: any }[]>;
texts: Record<string, Localized>; texts: Record<TextID, Localized>;
};
export type ItemConfig = { id: string, weight?: number, price?: number, power: number } & (ArmorConfig | WeaponConfig | WondrousConfig | MundaneConfig);
type ArmorConfig = {
category: 'armor';
name: string; //TODO -> TextID
description: TextID;
life: number;
absorb: number;
};
type WeaponConfig = {
category: 'armor';
name: string; //TODO -> TextID
description: TextID;
damage: Dice;
};
type WondrousConfig = {
category: 'armor';
name: string; //TODO -> TextID
description: TextID;
effect: FeatureEffect[];
};
type MundaneConfig = {
category: 'armor';
name: string; //TODO -> TextID
description: TextID;
}; };
export type SpellConfig = { export type SpellConfig = {
id: string; id: string;