diff --git a/db.sqlite b/db.sqlite index 7448ef7..1e2c1ea 100644 Binary files a/db.sqlite and b/db.sqlite differ diff --git a/db.sqlite-shm b/db.sqlite-shm index 198aaff..6c7c368 100644 Binary files a/db.sqlite-shm and b/db.sqlite-shm differ diff --git a/db.sqlite-wal b/db.sqlite-wal index 4ed15d2..9f33f59 100644 Binary files a/db.sqlite-wal and b/db.sqlite-wal differ diff --git a/db/schema.ts b/db/schema.ts index eba5829..4a24196 100644 --- a/db/schema.ts +++ b/db/schema.ts @@ -1,5 +1,6 @@ import { relations } from 'drizzle-orm'; 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", { id: int().primaryKey({ autoIncrement: true }), @@ -54,10 +55,9 @@ export const characterTable = table("character", { owner: int().notNull().references(() => usersTable.id, { onDelete: 'cascade', onUpdate: 'cascade' }), people: text().notNull(), level: int().notNull().default(1), + variables: text({ mode: 'json' }).notNull().default('{"health": 0,"mana": 0,"spells": [],"equipment": [],"exhaustion": 0,"sickness": []}'), aspect: int(), notes: text(), - health: int().notNull().default(0), - mana: int().notNull().default(0), visibility: text({ enum: ['private', 'public'] }).notNull().default('private'), thumbnail: blob(), @@ -83,17 +83,6 @@ export const characterAbilitiesTable = table("character_abilities", { max: int().notNull().default(0), }, (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", { character: int().notNull().references(() => characterTable.id, { onDelete: 'cascade', onUpdate: 'cascade' }), id: text().notNull(), @@ -123,8 +112,6 @@ export const characterRelation = relations(characterTable, ({ one, many }) => ({ training: many(characterTrainingTable), levels: many(characterLevelingTable), abilities: many(characterAbilitiesTable), - modifiers: many(characterModifiersTable), - spells: many(characterSpellsTable), choices: many(characterChoicesTable) })); @@ -137,12 +124,6 @@ export const characterLevelingRelation = relations(characterLevelingTable, ({ on export const characterAbilitiesRelation = relations(characterAbilitiesTable, ({ one }) => ({ 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 }) => ({ character: one(characterTable, { fields: [characterChoicesTable.character], references: [characterTable.id] }) })); \ No newline at end of file diff --git a/drizzle/0016_wild_the_anarchist.sql b/drizzle/0016_wild_the_anarchist.sql new file mode 100644 index 0000000..d5d1eda --- /dev/null +++ b/drizzle/0016_wild_the_anarchist.sql @@ -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`; \ No newline at end of file diff --git a/drizzle/meta/0016_snapshot.json b/drizzle/meta/0016_snapshot.json new file mode 100644 index 0000000..e8f81ca --- /dev/null +++ b/drizzle/meta/0016_snapshot.json @@ -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": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index 933c6f2..a8ccd84 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -113,6 +113,13 @@ "when": 1756214160038, "tag": "0015_typical_blade", "breakpoints": true + }, + { + "idx": 16, + "version": "6", + "when": 1756221197092, + "tag": "0016_wild_the_anarchist", + "breakpoints": true } ] } \ No newline at end of file diff --git a/pages/character/[id]/index.client.vue b/pages/character/[id]/index.client.vue index de56772..cff061d 100644 --- a/pages/character/[id]/index.client.vue +++ b/pages/character/[id]/index.client.vue @@ -13,7 +13,6 @@ const config = characterConfig as CharacterConfig; const id = useRouter().currentRoute.value.params.id; const { user } = useUserSession(); -const { add } = useToast(); const { data, status, error } = await useFetch(`/api/character/${id}`); 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
Niveau {{ character.level }} - {{ character.race === -1 ? "Race inconnue" : config.peoples[character.race]!.name }} + {{ config.peoples[character.race]?.name ?? 'Peuple inconnu' }}
@@ -132,7 +131,7 @@ text-light-purple dark:text-dark-purple border-light-purple dark:border-dark-pur Notes -
+
Actions @@ -157,9 +156,10 @@ text-light-purple dark:text-dark-purple border-light-purple dark:border-dark-pur
-
-
-
+
+
{{ character.variables.spells.length }} / {{ character.spellslots }} sorts maitrisés
+
+
{{ spell.name }}
@@ -170,7 +170,7 @@ text-light-purple dark:text-dark-purple border-light-purple dark:border-dark-pur Rang {{ spell.rank }}/ {{ spellTypeTexts[spell.type] }}/ {{ spell.cost }} mana/ - {{ typeof spell.speed === 'string' ? spell.speed : `${spell.speed} minutes` }} + {{ typeof spell.speed === 'string' ? spell.speed : `${spell.speed} minutes` }}
diff --git a/server/api/character.get.ts b/server/api/character.get.ts index ed1f20d..da9f8c7 100644 --- a/server/api/character.get.ts +++ b/server/api/character.get.ts @@ -3,7 +3,7 @@ import useDatabase from '~/composables/useDatabase'; import { characterTable, userPermissionsTable } from '~/db/schema'; import { hasPermissions } from '~/shared/auth.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) => { let { visibility } = getQuery(e) as { visibility?: "public" | "own" | "admin" }; @@ -55,8 +55,6 @@ export default defineEventHandler(async (e) => { with: { abilities: true, levels: true, - modifiers: true, - spells: true, training: true, choices: true, user: { @@ -76,14 +74,11 @@ export default defineEventHandler(async (e) => { level: character.level, aspect: character.aspect, notes: character.notes, - health: character.health, - mana: character.mana, + variables: character.variables, - 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[]>), - leveling: character.levels.map(e => [e.level as Level, e.choice] as DoubleIndex), + training: character.training.reduce((p, v) => { p[v.stat] ??= {}; p[v.stat][v.level as TrainingLevel] = v.choice; return p; }, {} as Record>>), + leveling: group(character.levels, "level", "choice"), 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), owner: character.owner, diff --git a/server/api/character.post.ts b/server/api/character.post.ts index 3e3ee73..6ce4992 100644 --- a/server/api/character.post.ts +++ b/server/api/character.post.ts @@ -1,6 +1,6 @@ import { z } from 'zod/v4'; 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 { type Ability, type MainStat } from '~/types/character'; @@ -32,8 +32,7 @@ export default defineEventHandler(async (e) => { level: body.data.level, aspect: body.data.aspect, notes: body.data.notes, - health: body.data.health, - mana: body.data.mana, + variables: body.data.variables, visibility: body.data.visibility, thumbnail: body.data.thumbnail, }).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]! }))); 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] })); if(abilities.length > 0) tx.insert(characterAbilitiesTable).values(abilities).run(); diff --git a/server/api/character/[id].get.ts b/server/api/character/[id].get.ts index 1d8a130..be34b35 100644 --- a/server/api/character/[id].get.ts +++ b/server/api/character/[id].get.ts @@ -1,8 +1,7 @@ -import { and, eq, sql } from 'drizzle-orm'; import useDatabase from '~/composables/useDatabase'; import { characterTable } from '~/db/schema'; 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) => { const id = getRouterParam(e, "id"); @@ -26,9 +25,8 @@ export default defineEventHandler(async (e) => { with: { abilities: true, levels: true, - modifiers: true, - spells: true, training: true, + choices: true, user: { columns: { username: true } } @@ -46,15 +44,12 @@ export default defineEventHandler(async (e) => { level: character.level, aspect: character.aspect, notes: character.notes, - health: character.health, - mana: character.mana, + variables: character.variables, training: character.training.reduce((p, v) => { p[v.stat] ??= {}; p[v.stat][v.level as TrainingLevel] = v.choice; return p; }, {} as Record>>), - leveling: character.levels.reduce((p, v) => { p[v.level as Level] = v.choice; return p; }, {} as Partial>), - abilities: group(character.abilities.map(e => ({ ...e, value: e.value })), "ability", "value"), - spells: character.spells.map(e => e.value), - modifiers: group(character.modifiers, "modifier", "value"), - choices: {}, + leveling: group(character.levels, "level", "choice"), + abilities: group(character.abilities, "ability", "value"), + choices: character.choices.reduce((p, v) => { p[v.id] ??= []; p[v.id]?.push(v.choice); return p; }, {} as Record), owner: character.owner, username: character.user.username, diff --git a/server/api/character/[id].post.ts b/server/api/character/[id].post.ts index 9ca3f74..abf1121 100644 --- a/server/api/character/[id].post.ts +++ b/server/api/character/[id].post.ts @@ -1,6 +1,6 @@ import { eq } from 'drizzle-orm'; 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 { type Ability, type MainStat } from '~/types/character'; @@ -43,16 +43,13 @@ export default defineEventHandler(async (e) => { level: body.data.level, aspect: body.data.aspect, notes: body.data.notes, - health: body.data.health, - mana: body.data.mana, + variables: body.data.variables, visibility: body.data.visibility, thumbnail: body.data.thumbnail, }).where(eq(characterTable.id, id)).run(); tx.delete(characterLevelingTable).where(eq(characterLevelingTable.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(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]! }))); 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 })); if(abilities.length > 0) tx.insert(characterAbilitiesTable).values(abilities).run(); }); diff --git a/server/api/character/[id]/duplicate.post.ts b/server/api/character/[id]/duplicate.post.ts index 7b26f12..7c645d1 100644 --- a/server/api/character/[id]/duplicate.post.ts +++ b/server/api/character/[id]/duplicate.post.ts @@ -1,6 +1,6 @@ import { eq } from 'drizzle-orm'; 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) => { const id = getRouterParam(e, "id"); @@ -36,8 +36,7 @@ export default defineEventHandler(async (e) => { level: old.level, aspect: old.aspect, notes: old.notes, - health: old.health, - mana: old.mana, + variables: old.variables, visibility: old.visibility, thumbnail: old.thumbnail, }).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(); 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(); if(abilities.length > 0) tx.insert(characterAbilitiesTable).values(abilities.map(e => ({ character: _id, ability: e.ability, value: e.value, max: e.max }))).run(); diff --git a/server/api/character/[id]/values.get.ts b/server/api/character/[id]/values.get.ts index 97d3b87..41d58b5 100644 --- a/server/api/character/[id]/values.get.ts +++ b/server/api/character/[id]/values.get.ts @@ -1,7 +1,7 @@ import { and, eq, sql } from 'drizzle-orm'; import useDatabase from '~/composables/useDatabase'; import { characterTable } from '~/db/schema'; -import type { Character, CharacterValues } from '~/types/character'; +import type { CharacterVariables } from '~/types/character'; export default defineEventHandler(async (e) => { const id = getRouterParam(e, "id"); @@ -27,7 +27,7 @@ export default defineEventHandler(async (e) => { if(character !== undefined) { - return character as CharacterValues; + return character as CharacterVariables; } setResponseStatus(e, 404); diff --git a/shared/character-config.json b/shared/character-config.json index 3a0156a..8d099a8 100644 --- a/shared/character-config.json +++ b/shared/character-config.json @@ -2675,6 +2675,89 @@ "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" + }, + { + "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": [ diff --git a/shared/character.util.ts b/shared/character.util.ts index 2fad7a0..3e2bbb9 100644 --- a/shared/character.util.ts +++ b/shared/character.util.ts @@ -25,15 +25,19 @@ export const defaultCharacter: Character = { name: "", people: undefined, level: 1, - health: 0, - mana: 0, training: MAIN_STATS.reduce((p, v) => { p[v] = { 0: 0 }; return p; }, {} as Record>>), leveling: { 1: 0 }, abilities: {}, - spells: [], - modifiers: {}, choices: {}, + variables: { + health: 0, + mana: 0, + spells: [], + equipment: [], + exhaustion: 0, + sickness: [], + }, owner: -1, visibility: "private", @@ -48,13 +52,7 @@ const defaultCompiledCharacter: (character: Character) => CompiledCharacter = (c race: character.people!, modifier: MAIN_STATS.reduce((p, v) => { p[v] = 0; return p; }, {} as Record), level: character.level, - variables: { - health: character.health, - mana: character.mana, - equipment: [], - exhaustion: 0, - sickness: [], - }, + variables: character.variables, action: 0, reaction: 0, exhaust: 0, @@ -121,7 +119,7 @@ const defaultCompiledCharacter: (character: Character) => CompiledCharacter = (c freeaction: [], reaction: [], passive: [], - spells: character.spells, + spells: character.variables.spells, }, aspect: "", notes: character.notes ?? "", @@ -174,17 +172,26 @@ export const spellTypeTexts: Record = { "instinct": "Instinct export const CharacterValidation = z.object({ id: z.number(), name: z.string(), - people: z.number().nullable(), + people: z.string().nullable(), level: z.number().min(1).max(20), aspect: z.number().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())), leveling: z.record(z.enum(LEVELS.map(String)), z.number()), abilities: z.record(z.enum(ABILITIES), z.number()), - spells: z.string().array(), 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(), username: z.string().optional(), visibility: z.enum(["public", "private"]), @@ -332,15 +339,17 @@ export class CharacterCompiler protected compile(properties: string[]) { const queue = properties; - queue.forEach(property => { + for(let i = 0; i < queue.length; i++) + { + const property = queue[i]!; const buffer = this._buffer[property]; if(property === "") - return + continue if(buffer && buffer._dirty === true) { - let sum = 0; + let sum = 0, shortcut = false; for(let i = 0; i < buffer.list.length; i++) { if(typeof buffer.list[i]!.value === 'string') // Add or set a modifier @@ -349,13 +358,16 @@ export class CharacterCompiler if(!modifier) { queue.push(property); - return; + shortcut = true; + break; } else if(modifier._dirty) { //Put it back in queue since its dependencies haven't been resolved yet + queue.push(buffer.list[i]!.value as string); queue.push(property); - return; + shortcut = true; + break; } else { @@ -374,6 +386,9 @@ export class CharacterCompiler } } + if(shortcut === true) + continue; + 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); @@ -383,7 +398,7 @@ export class CharacterCompiler this._buffer[property]!.value = sum; this._buffer[property]!._dirty = false; } - }); + } } } export class CharacterBuilder extends CharacterCompiler diff --git a/types/character.d.ts b/types/character.d.ts index 29ca385..cf49425 100644 --- a/types/character.d.ts +++ b/types/character.d.ts @@ -10,8 +10,11 @@ export type SpellElement = typeof SPELL_ELEMENTS[number]; export type Alignment = typeof ALIGNMENTS[number]; export type FeatureID = string; +export type TextID = string; export type Resistance = string; +export type Dice = `${number}d${4 | 6 | 8 | 10 | 12 | 20}`; + export type Character = { id: number; @@ -20,16 +23,12 @@ export type Character = { level: number; aspect?: number; notes?: string | null; - health: number; - mana: number; training: Record>>; leveling: Partial>; - abilities: Partial>; //First is the ability, second is the max increment - spells: string[]; //Spell ID - modifiers: Partial>; - - choices: Record; + abilities: Partial>; + variables: CharacterVariables; + choices: Record; owner: number; username?: string; @@ -40,8 +39,9 @@ export type CharacterVariables = { mana: number; exhaustion: number; - sickness: Array<{ id: string, progress: number | true }>; - equipment: Array; + sickness: Array<{ id: string, state: number | true }>; + spells: string[]; //Spell ID + equipment: string[]; //Equipment ID }; export type CharacterConfig = { peoples: Record; @@ -51,8 +51,35 @@ export type CharacterConfig = { spells: SpellConfig[]; aspects: AspectConfig[]; features: Record; + enchantments: Record; //TODO + items: Record; lists: Record; - texts: Record; + texts: Record; +}; +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 = { id: string;