You've already forked obsidian-visualiser
Compare commits
7 Commits
ab2778c626
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
df3577f673 | ||
|
|
871861e66e | ||
| 1ee895ab42 | |||
| 3f58114091 | |||
|
|
878bcc0a16 | ||
| e924fdfe38 | |||
| 5e6f296c56 |
45
components/base/Combobox.vue
Normal file
45
components/base/Combobox.vue
Normal file
@@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<Label class="my-2 flex flex-1 items-center justify-between flex-col md:flex-row">
|
||||
<span class="pb-1 md:p-0">{{ label }}</span>
|
||||
<ComboboxRoot v-model:model-value="model" v-model:open="open" :multiple="multiple">
|
||||
<ComboboxAnchor :disabled="disabled" class="mx-4 inline-flex min-w-[150px] items-center justify-between px-3 text-sm font-semibold leading-none h-8 gap-1
|
||||
bg-light-20 dark:bg-dark-20 border border-light-35 dark:border-dark-35 outline-none data-[placeholder]:font-normal
|
||||
data-[placeholder]:text-light-50 dark:data-[placeholder]:text-dark-50 focus:shadow-raw transition-[box-shadow] focus:shadow-light-40 dark:focus:shadow-dark-40
|
||||
hover:border-light-50 dark:hover:border-dark-50">
|
||||
<ComboboxTrigger class="flex flex-1 justify-between !cursor-pointer">
|
||||
<span v-if="!multiple">{{ model !== undefined ? options.find(e => e[1] === model)![0] : "" }}</span>
|
||||
<span class="flex gap-2" v-else><span v-if="model !== undefined">{{ options.find(e => e[1] === (model as T[])[0]) !== undefined ? options.find(e => e[1] === (model as T[])[0])![0] : undefined }}</span><span v-if="model !== undefined && (model as T[]).length > 1">{{((model as T[]).length > 1 ? `+${(model as T[]).length - 1}` : "") }}</span></span>
|
||||
<Icon icon="radix-icons:caret-down" class="h-4 w-4" />
|
||||
</ComboboxTrigger>
|
||||
</ComboboxAnchor>
|
||||
|
||||
<ComboboxPortal :disabled="disabled">
|
||||
<ComboboxContent :position="position" align="start" class="min-w-[150px] bg-light-20 dark:bg-dark-20 will-change-[opacity,transform] z-50">
|
||||
<ComboboxViewport>
|
||||
<ComboboxItem v-for="[label, value] of options" :value="value" :disabled="disabled" class="text-base py-2 leading-none text-light-60 dark:text-dark-60 flex items-center px-6 relative Combobox-none data-[disabled]:text-light-50 dark:data-[disabled]:text-dark-50 data-[disabled]:pointer-events-none data-[highlighted]:outline-none data-[highlighted]:bg-light-30 dark:data-[highlighted]:bg-dark-30 data-[highlighted]:text-light-100 dark:data-[highlighted]:text-dark-100">
|
||||
<span class="">{{ label }}</span>
|
||||
<ComboboxItemIndicator class="absolute left-1 w-4 inline-flex items-center justify-center">
|
||||
<Icon icon="radix-icons:check" />
|
||||
</ComboboxItemIndicator>
|
||||
</ComboboxItem>
|
||||
</ComboboxViewport>
|
||||
</ComboboxContent>
|
||||
</ComboboxPortal>
|
||||
</ComboboxRoot>
|
||||
</Label>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" generic="T extends string | number | boolean | Record<string, any>">
|
||||
import { ComboboxInput, ComboboxTrigger, ComboboxViewport, ComboboxContent, ComboboxPortal, ComboboxRoot } from 'radix-vue'
|
||||
import { Icon } from '@iconify/vue/dist/iconify.js';
|
||||
const { disabled = false, position = 'popper', multiple = false } = defineProps<{
|
||||
placeholder?: string
|
||||
disabled?: boolean
|
||||
position?: 'inline' | 'popper'
|
||||
label?: string
|
||||
multiple?: boolean
|
||||
options: Array<[string, T]>
|
||||
}>();
|
||||
const open = ref(false);
|
||||
const model = defineModel<T | T[]>();
|
||||
</script>
|
||||
BIN
db.sqlite-shm
BIN
db.sqlite-shm
Binary file not shown.
BIN
db.sqlite-wal
BIN
db.sqlite-wal
Binary file not shown.
82
db/schema.ts
82
db/schema.ts
@@ -1,5 +1,6 @@
|
||||
import { relations } from 'drizzle-orm';
|
||||
import { int, text, sqliteTable, type SQLiteTableExtraConfig, primaryKey, blob } from 'drizzle-orm/sqlite-core';
|
||||
import { int, text, sqliteTable, primaryKey, blob } from 'drizzle-orm/sqlite-core';
|
||||
import { ABILITIES, MAIN_STATS } from '../types/character';
|
||||
|
||||
export const usersTable = sqliteTable("users", {
|
||||
id: int().primaryKey({ autoIncrement: true }),
|
||||
@@ -20,20 +21,12 @@ export const userSessionsTable = sqliteTable("user_sessions", {
|
||||
id: int().notNull(),
|
||||
user_id: int().notNull().references(() => usersTable.id, { onDelete: 'cascade', onUpdate: 'cascade' }),
|
||||
timestamp: int({ mode: 'timestamp' }).notNull().$defaultFn(() => new Date()),
|
||||
}, (table): SQLiteTableExtraConfig => {
|
||||
return {
|
||||
pk: primaryKey({ columns: [table.id, table.user_id] }),
|
||||
}
|
||||
});
|
||||
}, (table) => [primaryKey({ columns: [table.id, table.user_id] })]);
|
||||
|
||||
export const userPermissionsTable = sqliteTable("user_permissions", {
|
||||
id: int().notNull().references(() => usersTable.id, { onDelete: 'cascade', onUpdate: 'cascade' }),
|
||||
permission: text().notNull(),
|
||||
}, (table): SQLiteTableExtraConfig => {
|
||||
return {
|
||||
pk: primaryKey({ columns: [table.id, table.permission] }),
|
||||
}
|
||||
});
|
||||
}, (table) => [primaryKey({ columns: [table.id, table.permission] })]);
|
||||
|
||||
export const explorerContentTable = sqliteTable("explorer_content", {
|
||||
path: text().primaryKey(),
|
||||
@@ -57,9 +50,47 @@ export const characterTable = sqliteTable("character", {
|
||||
id: int().primaryKey({ autoIncrement: true }),
|
||||
name: text().notNull(),
|
||||
owner: int().notNull().references(() => usersTable.id, { onDelete: 'cascade', onUpdate: 'cascade' }),
|
||||
progress: text({ mode: 'json' }).notNull(),
|
||||
people: int().notNull(),
|
||||
level: int().notNull().default(1),
|
||||
aspect: int(),
|
||||
notes: text(),
|
||||
health: int().notNull().default(0),
|
||||
mana: int().notNull().default(0),
|
||||
|
||||
visibility: text({ enum: ['private', 'public'] }).notNull().default('private'),
|
||||
thumbnail: blob(),
|
||||
})
|
||||
});
|
||||
|
||||
export const characterTrainingTable = sqliteTable("character_training", {
|
||||
character: int().notNull().references(() => characterTable.id, { onDelete: 'cascade', onUpdate: 'cascade' }),
|
||||
stat: text({ enum: MAIN_STATS }).notNull(),
|
||||
level: int().notNull(),
|
||||
choice: int().notNull(),
|
||||
}, (table) => [primaryKey({ columns: [table.character, table.stat, table.level] })]);
|
||||
|
||||
export const characterLevelingTable = sqliteTable("character_leveling", {
|
||||
character: int().notNull().references(() => characterTable.id, { onDelete: 'cascade', onUpdate: 'cascade' }),
|
||||
level: int().notNull(),
|
||||
choice: int().notNull(),
|
||||
}, (table) => [primaryKey({ columns: [table.character, table.level] })]);
|
||||
|
||||
export const characterAbilitiesTable = sqliteTable("character_abilities", {
|
||||
character: int().notNull().references(() => characterTable.id, { onDelete: 'cascade', onUpdate: 'cascade' }),
|
||||
ability: text({ enum: ABILITIES }).notNull(),
|
||||
value: int().notNull().default(0),
|
||||
max: int().notNull().default(0),
|
||||
}, (table) => [primaryKey({ columns: [table.character, table.ability] })]);
|
||||
|
||||
export const characterModifiersTable = sqliteTable("character_modifiers", {
|
||||
character: int().notNull().references(() => characterTable.id, { onDelete: 'cascade', onUpdate: 'cascade' }),
|
||||
modifier: text({ enum: MAIN_STATS }).notNull(),
|
||||
value: int().notNull().default(0),
|
||||
}, (table) => [primaryKey({ columns: [table.character, table.modifier] })]);
|
||||
|
||||
export const characterSpellsTable = sqliteTable("character_spell", {
|
||||
character: int().notNull().references(() => characterTable.id, { onDelete: 'cascade', onUpdate: 'cascade' }),
|
||||
value: text().notNull(),
|
||||
}, (table) => [primaryKey({ columns: [table.character, table.value] })]);
|
||||
|
||||
export const usersRelation = relations(usersTable, ({ one, many }) => ({
|
||||
data: one(usersDataTable, { fields: [usersTable.id], references: [usersDataTable.id], }),
|
||||
@@ -79,6 +110,27 @@ export const userPermissionsRelation = relations(userPermissionsTable, ({ one })
|
||||
export const explorerContentRelation = relations(explorerContentTable, ({ one }) => ({
|
||||
users: one(usersTable, { fields: [explorerContentTable.owner], references: [usersTable.id], }),
|
||||
}));
|
||||
export const characterRelation = relations(characterTable, ({ one }) => ({
|
||||
users: one(usersTable, { fields: [characterTable.owner], references: [usersTable.id], }),
|
||||
export const characterRelation = relations(characterTable, ({ one, many }) => ({
|
||||
user: one(usersTable, { fields: [characterTable.owner], references: [usersTable.id], }),
|
||||
training: many(characterTrainingTable),
|
||||
levels: many(characterLevelingTable),
|
||||
abilities: many(characterAbilitiesTable),
|
||||
modifiers: many(characterModifiersTable),
|
||||
spells: many(characterSpellsTable)
|
||||
}));
|
||||
|
||||
export const characterTrainingRelation = relations(characterTrainingTable, ({ one }) => ({
|
||||
character: one(characterTable, { fields: [characterTrainingTable.character], references: [characterTable.id] })
|
||||
}));
|
||||
export const characterLevelingRelation = relations(characterLevelingTable, ({ one }) => ({
|
||||
character: one(characterTable, { fields: [characterLevelingTable.character], references: [characterTable.id] })
|
||||
}));
|
||||
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] })
|
||||
}));
|
||||
1
drizzle/0008_glorious_johnny_blaze.sql
Normal file
1
drizzle/0008_glorious_johnny_blaze.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE `character` ADD `values` text DEFAULT '{}' NOT NULL;
|
||||
1
drizzle/0009_thin_omega_sentinel.sql
Normal file
1
drizzle/0009_thin_omega_sentinel.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE `character` ADD `visibility` text DEFAULT 'private' NOT NULL;
|
||||
47
drizzle/0010_bored_sabra.sql
Normal file
47
drizzle/0010_bored_sabra.sql
Normal file
@@ -0,0 +1,47 @@
|
||||
CREATE TABLE `character_abilities` (
|
||||
`character` integer NOT NULL,
|
||||
`ability` text NOT NULL,
|
||||
`value` integer DEFAULT 0 NOT NULL,
|
||||
PRIMARY KEY(`character`, `ability`),
|
||||
FOREIGN KEY (`character`) REFERENCES `character`(`id`) ON UPDATE cascade ON DELETE cascade
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE `character_leveling` (
|
||||
`character` integer NOT NULL,
|
||||
`level` integer NOT NULL,
|
||||
`choice` integer NOT NULL,
|
||||
PRIMARY KEY(`character`, `level`),
|
||||
FOREIGN KEY (`character`) REFERENCES `character`(`id`) ON UPDATE cascade ON DELETE cascade
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE `character_modifiers` (
|
||||
`character` integer NOT NULL,
|
||||
`modifier` text NOT NULL,
|
||||
`value` integer DEFAULT 0 NOT NULL,
|
||||
PRIMARY KEY(`character`, `modifier`),
|
||||
FOREIGN KEY (`character`) REFERENCES `character`(`id`) ON UPDATE cascade ON DELETE cascade
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE `character_spell` (
|
||||
`character` integer PRIMARY KEY NOT NULL,
|
||||
`value` text NOT NULL,
|
||||
FOREIGN KEY (`character`) REFERENCES `character`(`id`) ON UPDATE cascade ON DELETE cascade
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE `character_training` (
|
||||
`character` integer NOT NULL,
|
||||
`stat` text NOT NULL,
|
||||
`level` integer NOT NULL,
|
||||
`choice` integer NOT NULL,
|
||||
PRIMARY KEY(`character`, `stat`, `level`),
|
||||
FOREIGN KEY (`character`) REFERENCES `character`(`id`) ON UPDATE cascade ON DELETE cascade
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE `character` ADD `people` integer NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE `character` ADD `level` integer DEFAULT 1 NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE `character` ADD `aspect` integer;--> statement-breakpoint
|
||||
ALTER TABLE `character` ADD `notes` text;--> statement-breakpoint
|
||||
ALTER TABLE `character` ADD `health` integer DEFAULT 0 NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE `character` ADD `mana` integer DEFAULT 0 NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE `character` DROP COLUMN `progress`;--> statement-breakpoint
|
||||
ALTER TABLE `character` DROP COLUMN `values`;
|
||||
1
drizzle/0011_demonic_titania.sql
Normal file
1
drizzle/0011_demonic_titania.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE `character_abilities` ADD `max` integer DEFAULT 0 NOT NULL;
|
||||
12
drizzle/0012_graceful_energizer.sql
Normal file
12
drizzle/0012_graceful_energizer.sql
Normal file
@@ -0,0 +1,12 @@
|
||||
PRAGMA foreign_keys=OFF;--> statement-breakpoint
|
||||
CREATE TABLE `__new_character_spell` (
|
||||
`character` integer NOT NULL,
|
||||
`value` text NOT NULL,
|
||||
PRIMARY KEY(`character`, `value`),
|
||||
FOREIGN KEY (`character`) REFERENCES `character`(`id`) ON UPDATE cascade ON DELETE cascade
|
||||
);
|
||||
--> statement-breakpoint
|
||||
INSERT INTO `__new_character_spell`("character", "value") SELECT "character", "value" FROM `character_spell`;--> statement-breakpoint
|
||||
DROP TABLE `character_spell`;--> statement-breakpoint
|
||||
ALTER TABLE `__new_character_spell` RENAME TO `character_spell`;--> statement-breakpoint
|
||||
PRAGMA foreign_keys=ON;
|
||||
426
drizzle/meta/0008_snapshot.json
Normal file
426
drizzle/meta/0008_snapshot.json
Normal file
@@ -0,0 +1,426 @@
|
||||
{
|
||||
"version": "6",
|
||||
"dialect": "sqlite",
|
||||
"id": "eb68cf2f-c7e2-4111-910d-a26b0fc438cc",
|
||||
"prevId": "15ea15e0-3d44-4dff-a4cd-f8666c4aa5ed",
|
||||
"tables": {
|
||||
"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
|
||||
},
|
||||
"progress": {
|
||||
"name": "progress",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"values": {
|
||||
"name": "values",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "'{}'"
|
||||
},
|
||||
"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": {}
|
||||
},
|
||||
"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": {}
|
||||
},
|
||||
"explorer_content": {
|
||||
"name": "explorer_content",
|
||||
"columns": {
|
||||
"path": {
|
||||
"name": "path",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"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
|
||||
},
|
||||
"content": {
|
||||
"name": "content",
|
||||
"type": "blob",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"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
|
||||
},
|
||||
"visit": {
|
||||
"name": "visit",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
},
|
||||
"timestamp": {
|
||||
"name": "timestamp",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"explorer_content_owner_users_id_fk": {
|
||||
"name": "explorer_content_owner_users_id_fk",
|
||||
"tableFrom": "explorer_content",
|
||||
"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
|
||||
},
|
||||
"logCount": {
|
||||
"name": "logCount",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"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": {}
|
||||
}
|
||||
}
|
||||
434
drizzle/meta/0009_snapshot.json
Normal file
434
drizzle/meta/0009_snapshot.json
Normal file
@@ -0,0 +1,434 @@
|
||||
{
|
||||
"version": "6",
|
||||
"dialect": "sqlite",
|
||||
"id": "bffde16c-d716-40ec-9d92-cb49814815d7",
|
||||
"prevId": "eb68cf2f-c7e2-4111-910d-a26b0fc438cc",
|
||||
"tables": {
|
||||
"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
|
||||
},
|
||||
"progress": {
|
||||
"name": "progress",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"values": {
|
||||
"name": "values",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "'{}'"
|
||||
},
|
||||
"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": {}
|
||||
},
|
||||
"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": {}
|
||||
},
|
||||
"explorer_content": {
|
||||
"name": "explorer_content",
|
||||
"columns": {
|
||||
"path": {
|
||||
"name": "path",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"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
|
||||
},
|
||||
"content": {
|
||||
"name": "content",
|
||||
"type": "blob",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"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
|
||||
},
|
||||
"visit": {
|
||||
"name": "visit",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
},
|
||||
"timestamp": {
|
||||
"name": "timestamp",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"explorer_content_owner_users_id_fk": {
|
||||
"name": "explorer_content_owner_users_id_fk",
|
||||
"tableFrom": "explorer_content",
|
||||
"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
|
||||
},
|
||||
"logCount": {
|
||||
"name": "logCount",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"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": {}
|
||||
}
|
||||
}
|
||||
724
drizzle/meta/0010_snapshot.json
Normal file
724
drizzle/meta/0010_snapshot.json
Normal file
@@ -0,0 +1,724 @@
|
||||
{
|
||||
"version": "6",
|
||||
"dialect": "sqlite",
|
||||
"id": "af3d9e4f-cea6-42fa-8f8b-d743d97b9c37",
|
||||
"prevId": "bffde16c-d716-40ec-9d92-cb49814815d7",
|
||||
"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
|
||||
}
|
||||
},
|
||||
"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_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_modifiers": {
|
||||
"name": "character_modifiers",
|
||||
"columns": {
|
||||
"character": {
|
||||
"name": "character",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"modifier": {
|
||||
"name": "modifier",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"value": {
|
||||
"name": "value",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"character_modifiers_character_character_id_fk": {
|
||||
"name": "character_modifiers_character_character_id_fk",
|
||||
"tableFrom": "character_modifiers",
|
||||
"tableTo": "character",
|
||||
"columnsFrom": [
|
||||
"character"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"character_modifiers_character_modifier_pk": {
|
||||
"columns": [
|
||||
"character",
|
||||
"modifier"
|
||||
],
|
||||
"name": "character_modifiers_character_modifier_pk"
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"character_spell": {
|
||||
"name": "character_spell",
|
||||
"columns": {
|
||||
"character": {
|
||||
"name": "character",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"value": {
|
||||
"name": "value",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"character_spell_character_character_id_fk": {
|
||||
"name": "character_spell_character_character_id_fk",
|
||||
"tableFrom": "character_spell",
|
||||
"tableTo": "character",
|
||||
"columnsFrom": [
|
||||
"character"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"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": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"level": {
|
||||
"name": "level",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 1
|
||||
},
|
||||
"aspect": {
|
||||
"name": "aspect",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"notes": {
|
||||
"name": "notes",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"health": {
|
||||
"name": "health",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
},
|
||||
"mana": {
|
||||
"name": "mana",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
},
|
||||
"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": {}
|
||||
},
|
||||
"explorer_content": {
|
||||
"name": "explorer_content",
|
||||
"columns": {
|
||||
"path": {
|
||||
"name": "path",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"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
|
||||
},
|
||||
"content": {
|
||||
"name": "content",
|
||||
"type": "blob",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"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
|
||||
},
|
||||
"visit": {
|
||||
"name": "visit",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
},
|
||||
"timestamp": {
|
||||
"name": "timestamp",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"explorer_content_owner_users_id_fk": {
|
||||
"name": "explorer_content_owner_users_id_fk",
|
||||
"tableFrom": "explorer_content",
|
||||
"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
|
||||
},
|
||||
"logCount": {
|
||||
"name": "logCount",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"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": {}
|
||||
}
|
||||
}
|
||||
732
drizzle/meta/0011_snapshot.json
Normal file
732
drizzle/meta/0011_snapshot.json
Normal file
@@ -0,0 +1,732 @@
|
||||
{
|
||||
"version": "6",
|
||||
"dialect": "sqlite",
|
||||
"id": "e0aaebf1-54e4-4f61-804b-7cce23c88069",
|
||||
"prevId": "af3d9e4f-cea6-42fa-8f8b-d743d97b9c37",
|
||||
"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_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_modifiers": {
|
||||
"name": "character_modifiers",
|
||||
"columns": {
|
||||
"character": {
|
||||
"name": "character",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"modifier": {
|
||||
"name": "modifier",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"value": {
|
||||
"name": "value",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"character_modifiers_character_character_id_fk": {
|
||||
"name": "character_modifiers_character_character_id_fk",
|
||||
"tableFrom": "character_modifiers",
|
||||
"tableTo": "character",
|
||||
"columnsFrom": [
|
||||
"character"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"character_modifiers_character_modifier_pk": {
|
||||
"columns": [
|
||||
"character",
|
||||
"modifier"
|
||||
],
|
||||
"name": "character_modifiers_character_modifier_pk"
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"character_spell": {
|
||||
"name": "character_spell",
|
||||
"columns": {
|
||||
"character": {
|
||||
"name": "character",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"value": {
|
||||
"name": "value",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"character_spell_character_character_id_fk": {
|
||||
"name": "character_spell_character_character_id_fk",
|
||||
"tableFrom": "character_spell",
|
||||
"tableTo": "character",
|
||||
"columnsFrom": [
|
||||
"character"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"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": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"level": {
|
||||
"name": "level",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 1
|
||||
},
|
||||
"aspect": {
|
||||
"name": "aspect",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"notes": {
|
||||
"name": "notes",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"health": {
|
||||
"name": "health",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
},
|
||||
"mana": {
|
||||
"name": "mana",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
},
|
||||
"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": {}
|
||||
},
|
||||
"explorer_content": {
|
||||
"name": "explorer_content",
|
||||
"columns": {
|
||||
"path": {
|
||||
"name": "path",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"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
|
||||
},
|
||||
"content": {
|
||||
"name": "content",
|
||||
"type": "blob",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"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
|
||||
},
|
||||
"visit": {
|
||||
"name": "visit",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
},
|
||||
"timestamp": {
|
||||
"name": "timestamp",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"explorer_content_owner_users_id_fk": {
|
||||
"name": "explorer_content_owner_users_id_fk",
|
||||
"tableFrom": "explorer_content",
|
||||
"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
|
||||
},
|
||||
"logCount": {
|
||||
"name": "logCount",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"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": {}
|
||||
}
|
||||
}
|
||||
740
drizzle/meta/0012_snapshot.json
Normal file
740
drizzle/meta/0012_snapshot.json
Normal file
@@ -0,0 +1,740 @@
|
||||
{
|
||||
"version": "6",
|
||||
"dialect": "sqlite",
|
||||
"id": "cb7a2b9c-1392-4f23-9fc2-9ce8de2e0231",
|
||||
"prevId": "e0aaebf1-54e4-4f61-804b-7cce23c88069",
|
||||
"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_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_modifiers": {
|
||||
"name": "character_modifiers",
|
||||
"columns": {
|
||||
"character": {
|
||||
"name": "character",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"modifier": {
|
||||
"name": "modifier",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"value": {
|
||||
"name": "value",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"character_modifiers_character_character_id_fk": {
|
||||
"name": "character_modifiers_character_character_id_fk",
|
||||
"tableFrom": "character_modifiers",
|
||||
"tableTo": "character",
|
||||
"columnsFrom": [
|
||||
"character"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"character_modifiers_character_modifier_pk": {
|
||||
"columns": [
|
||||
"character",
|
||||
"modifier"
|
||||
],
|
||||
"name": "character_modifiers_character_modifier_pk"
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"character_spell": {
|
||||
"name": "character_spell",
|
||||
"columns": {
|
||||
"character": {
|
||||
"name": "character",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"value": {
|
||||
"name": "value",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"character_spell_character_character_id_fk": {
|
||||
"name": "character_spell_character_character_id_fk",
|
||||
"tableFrom": "character_spell",
|
||||
"tableTo": "character",
|
||||
"columnsFrom": [
|
||||
"character"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"character_spell_character_value_pk": {
|
||||
"columns": [
|
||||
"character",
|
||||
"value"
|
||||
],
|
||||
"name": "character_spell_character_value_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": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"level": {
|
||||
"name": "level",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 1
|
||||
},
|
||||
"aspect": {
|
||||
"name": "aspect",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"notes": {
|
||||
"name": "notes",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"health": {
|
||||
"name": "health",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
},
|
||||
"mana": {
|
||||
"name": "mana",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
},
|
||||
"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": {}
|
||||
},
|
||||
"explorer_content": {
|
||||
"name": "explorer_content",
|
||||
"columns": {
|
||||
"path": {
|
||||
"name": "path",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"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
|
||||
},
|
||||
"content": {
|
||||
"name": "content",
|
||||
"type": "blob",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"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
|
||||
},
|
||||
"visit": {
|
||||
"name": "visit",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
},
|
||||
"timestamp": {
|
||||
"name": "timestamp",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"explorer_content_owner_users_id_fk": {
|
||||
"name": "explorer_content_owner_users_id_fk",
|
||||
"tableFrom": "explorer_content",
|
||||
"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
|
||||
},
|
||||
"logCount": {
|
||||
"name": "logCount",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"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": {}
|
||||
}
|
||||
}
|
||||
@@ -57,6 +57,41 @@
|
||||
"when": 1745074613379,
|
||||
"tag": "0007_tearful_true_believers",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 8,
|
||||
"version": "6",
|
||||
"when": 1745675022171,
|
||||
"tag": "0008_glorious_johnny_blaze",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 9,
|
||||
"version": "6",
|
||||
"when": 1745920443528,
|
||||
"tag": "0009_thin_omega_sentinel",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 10,
|
||||
"version": "6",
|
||||
"when": 1746014143374,
|
||||
"tag": "0010_bored_sabra",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 11,
|
||||
"version": "6",
|
||||
"when": 1746017162319,
|
||||
"tag": "0011_demonic_titania",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 12,
|
||||
"version": "6",
|
||||
"when": 1746027790969,
|
||||
"tag": "0012_graceful_energizer",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -14,10 +14,21 @@
|
||||
<span class="text-xl max-md:hidden">d[any]</span>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
<div class="flex items-center gap-8 max-md:hidden">
|
||||
<Tooltip message="Developpement en cours" side="bottom"><NuxtLink href="#" class="text-light-70 dark:text-dark-70">Parcourir les projets</NuxtLink></Tooltip>
|
||||
<Tooltip message="Developpement en cours" side="bottom"><NuxtLink href="#" class="text-light-70 dark:text-dark-70">Créer du contenu</NuxtLink></Tooltip>
|
||||
</div>
|
||||
<NavigationMenuRoot class="relative">
|
||||
<NavigationMenuList class="flex items-center gap-8 max-md:hidden">
|
||||
<NavigationMenuItem>
|
||||
<NavigationMenuTrigger>
|
||||
<NuxtLink :href="{ name: 'character' }" class="text-light-70 dark:text-dark-70" active-class="!text-accent-blue"><span class="pl-3 py-1 flex-1 truncate">Personnages</span></NuxtLink>
|
||||
</NavigationMenuTrigger>
|
||||
<NavigationMenuContent class="absolute top-0 left-0 w-full sm:w-auto bg-light-0 dark:bg-dark-0 border border-light-30 dark:border-dark-30">
|
||||
<NuxtLink :href="{ name: 'character-list' }" class="text-light-70 dark:text-dark-70" active-class="!text-accent-blue"><span class="py-2 px-3 flex-1 truncate">Tous les personnages</span></NuxtLink>
|
||||
</NavigationMenuContent>
|
||||
</NavigationMenuItem>
|
||||
</NavigationMenuList>
|
||||
<div class="absolute top-full left-0 flex w-full justify-center my-4">
|
||||
<NavigationMenuViewport class="h-[var(--radix-navigation-menu-viewport-height)] w-full origin-[top_center] overflow-hidden rounded-[10px] bg-white transition-[width,_height] duration-300 sm:w-[var(--radix-navigation-menu-viewport-width)]" />
|
||||
</div>
|
||||
</NavigationMenuRoot>
|
||||
<div class="flex items-center px-2 gap-4">
|
||||
<template v-if="!loggedIn">
|
||||
<NuxtLink class="text-light-100 dark:text-dark-100 hover:text-light-70 dark:hover:text-dark-70" :to="{ name: 'user-login' }">Se connecter</NuxtLink>
|
||||
@@ -33,9 +44,6 @@
|
||||
<div class="bg-light-0 dark:bg-dark-0 z-40 w-screen md:w-[18rem] border-r border-light-30 dark:border-dark-30 flex flex-col justify-between my-2 max-md:data-[state=closed]:hidden">
|
||||
<div class="flex-1 px-2 max-w-full max-h-full overflow-y-auto overflow-x-hidden">
|
||||
<div v-if="user" class="flex flex-1 py-4 px-2 flex-row flex-1 justify-between items-center">
|
||||
<NuxtLink :href="{ name: 'character' }" class="flex flex-1 font-bold text-lg items-center border-light-35 dark:border-dark-35 hover:border-accent-blue" active-class="text-accent-blue border-s-2 !border-accent-blue">
|
||||
<span class="pl-3 py-1 flex-1 truncate">Mes personnages</span>
|
||||
</NuxtLink>
|
||||
<NuxtLink v-if="hasPermissions(user.permissions, ['admin', 'editor'])" :to="{ name: 'explore-edit' }"><Button icon><Icon icon="radix-icons:pencil-2" /></Button></NuxtLink>
|
||||
</div>
|
||||
<Tree v-if="pages" v-model="pages" :getKey="(item) => item.path" class="ps-4">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import config from '#shared/character-config.json';
|
||||
|
||||
function raceOptionToText(option: RaceOption): string
|
||||
{
|
||||
const text = [];
|
||||
@@ -9,6 +10,7 @@ function raceOptionToText(option: RaceOption): string
|
||||
if(option.abilities) text.push(`+${option.abilities} point${option.abilities > 1 ? 's' : ''} de compétence${option.abilities > 1 ? 's' : ''}.`);
|
||||
if(option.health) text.push(`+${option.health} PV max.`);
|
||||
if(option.mana) text.push(`+${option.mana} mana max.`);
|
||||
if(option.spellslots) text.push(`+${option.spellslots} sort${option.spellslots > 1 ? 's' : ''} maitrisé${option.spellslots > 1 ? 's' : ''}.`);
|
||||
return text.join('\n');
|
||||
}
|
||||
function getFeaturesOf(stat: MainStat, progression: DoubleIndex<TrainingLevel>[]): TrainingOption[]
|
||||
@@ -17,15 +19,7 @@ function getFeaturesOf(stat: MainStat, progression: DoubleIndex<TrainingLevel>[]
|
||||
return progression.map(e => characterData.training[stat][e[0]][e[1]]);
|
||||
}
|
||||
|
||||
const mainStatTexts: Record<MainStat, string> = {
|
||||
"strength": "Force",
|
||||
"dexterity": "Dextérité",
|
||||
"constitution": "Constitution",
|
||||
"intelligence": "Intelligence",
|
||||
"curiosity": "Curiosité",
|
||||
"charisma": "Charisme",
|
||||
"psyche": "Psyché",
|
||||
}
|
||||
|
||||
|
||||
function abilitySpecialFeatures(type: "points" | "max", curiosity: DoubleIndex<TrainingLevel>[], value: number): number
|
||||
{
|
||||
@@ -43,7 +37,7 @@ function abilitySpecialFeatures(type: "points" | "max", curiosity: DoubleIndex<T
|
||||
import { Icon } from '@iconify/vue/dist/iconify.js';
|
||||
import PreviewA from '~/components/prose/PreviewA.vue';
|
||||
import { clamp } from '~/shared/general.util';
|
||||
import type { Ability, Character, CharacterConfig, DoubleIndex, Level, MainStat, RaceOption, TrainingLevel, TrainingOption } from '~/types/character';
|
||||
import { defaultCharacter, elementTexts, mainStatTexts, spellTypeTexts, type Ability, type Character, type CharacterConfig, type DoubleIndex, type Level, type MainStat, type RaceOption, type SpellConfig, type SpellElement, type SpellType, type TrainingLevel, type TrainingOption } from '~/types/character';
|
||||
|
||||
definePageMeta({
|
||||
guestsGoesTo: '/user/login',
|
||||
@@ -51,43 +45,36 @@ definePageMeta({
|
||||
let id = useRouter().currentRoute.value.params.id;
|
||||
const { add } = useToast();
|
||||
const characterConfig = config as CharacterConfig;
|
||||
const data = ref<Character>({
|
||||
id: -1,
|
||||
name: '',
|
||||
progress: {
|
||||
training: {
|
||||
strength: [[1, 0], [2, 0], [3, 0], [4, 0]],
|
||||
dexterity: [[1, 0], [2, 0], [3, 0], [4, 0]],
|
||||
constitution: [[1, 0], [2, 0], [3, 0], [4, 0]],
|
||||
intelligence: [[1, 0], [2, 0], [3, 0], [4, 0]],
|
||||
curiosity: [[1, 0], [2, 0], [3, 0], [4, 0]],
|
||||
charisma: [[1, 0], [2, 0], [3, 0], [4, 0]],
|
||||
psyche: [[1, 0], [2, 0], [3, 0], [4, 0]],
|
||||
},
|
||||
level: 1,
|
||||
race: {
|
||||
index: undefined,
|
||||
progress: [[1, 0]],
|
||||
},
|
||||
abilities: {},
|
||||
modifiers: {},
|
||||
notes: "",
|
||||
},
|
||||
const data = ref<Character>({ ...defaultCharacter });
|
||||
const spellFilter = ref<{
|
||||
ranks: Array<1 | 2 | 3>,
|
||||
types: Array<SpellType>,
|
||||
text: string,
|
||||
elements: Array<SpellElement>,
|
||||
tags: string[],
|
||||
}>({
|
||||
ranks: [],
|
||||
types: [],
|
||||
text: "",
|
||||
elements: [],
|
||||
tags: [],
|
||||
});
|
||||
|
||||
const peopleOpen = ref(false), trainingOpen = ref(false), notesOpen = ref(false), abilityOpen = ref(false), trainingTab = ref(0);
|
||||
const raceOptions = computed(() => data.value.progress.race.index !== undefined ? characterConfig.peoples[data.value.progress.race.index!].options : undefined);
|
||||
const selectedRaceOptions = computed(() => raceOptions !== undefined ? data.value.progress.race.progress!.map(e => raceOptions.value![e[0]][e[1]]) : undefined);
|
||||
const trainingPoints = computed(() => raceOptions.value ? data.value.progress.race.progress?.reduce((p, v) => p + (raceOptions.value![v[0]][v[1]].training ?? 0), 0) : 0);
|
||||
const training = computed(() => Object.entries(characterConfig.training).map(e => [e[0], getFeaturesOf(e[0] as MainStat, data.value.progress.training[e[0] as MainStat])]) as [MainStat, TrainingOption[]][]);
|
||||
const maxTraining = computed(() => Object.entries(data.value.progress.training).reduce((p, v) => { p[v[0] as MainStat] = v[1].reduce((_p, _v) => Math.max(_p, _v[0]) , 0); return p; }, {} as Record<MainStat, number>));
|
||||
const peopleOpen = ref(false), trainingOpen = ref(false), abilityOpen = ref(false), spellOpen = ref(false), notesOpen = ref(false), trainingTab = ref(0);
|
||||
const raceOptions = computed(() => data.value.people !== undefined ? characterConfig.peoples[data.value.people!].options : undefined);
|
||||
const selectedRaceOptions = computed(() => raceOptions !== undefined ? data.value.leveling!.map(e => raceOptions.value![e[0]][e[1]]) : undefined);
|
||||
const trainingPoints = computed(() => raceOptions.value ? data.value.leveling?.reduce((p, v) => p + (raceOptions.value![v[0]][v[1]].training ?? 0), 0) : 0);
|
||||
const training = computed(() => Object.entries(characterConfig.training).map(e => [e[0], getFeaturesOf(e[0] as MainStat, data.value.training[e[0] as MainStat])]) as [MainStat, TrainingOption[]][]);
|
||||
const maxTraining = computed(() => Object.entries(data.value.training).reduce((p, v) => { p[v[0] as MainStat] = v[1].reduce((_p, _v) => Math.max(_p, _v[0]) , 0); return p; }, {} as Record<MainStat, number>));
|
||||
const trainingSpent = computed(() => Object.values(maxTraining.value).reduce((p, v) => p + v, 0));
|
||||
const modifiers = computed(() => Object.entries(maxTraining.value).reduce((p, v) => { p[v[0] as MainStat] = Math.floor(v[1] / 3) + (data.value.progress.modifiers ? (data.value.progress.modifiers[v[0] as MainStat] ?? 0) : 0); return p; }, {} as Record<MainStat, number>))
|
||||
const modifiers = computed(() => Object.entries(maxTraining.value).reduce((p, v) => { p[v[0] as MainStat] = Math.floor(v[1] / 3) + (data.value.modifiers ? (data.value.modifiers[v[0] as MainStat] ?? 0) : 0); return p; }, {} as Record<MainStat, number>))
|
||||
const modifierPoints = computed(() => (selectedRaceOptions.value ? selectedRaceOptions.value.reduce((p, v) => p + (v?.modifier ?? 0), 0) : 0) + training.value.reduce((p, v) => p + v[1].reduce((_p, _v) => _p + (_v?.modifier ?? 0), 0), 0));
|
||||
const modifierSpent = computed(() => Object.values(data.value.progress.modifiers ?? {}).reduce((p, v) => p + v, 0));
|
||||
const abilityPoints = computed(() => training.value.flatMap(e => e[1].filter(_e => _e.ability !== undefined)).reduce((p, v) => p + v.ability!, 0));
|
||||
const abilityMax = computed(() => Object.entries(characterConfig.abilities).reduce((p, v) => { p[v[0] as Ability] = abilitySpecialFeatures("max", data.value.progress.training.curiosity, Math.floor(maxTraining.value[v[1].max[0]] / 3) + Math.floor(maxTraining.value[v[1].max[1]] / 3)); return p; }, {} as Record<Ability, number>));
|
||||
const abilitySpent = computed(() => Object.values(data.value.progress.abilities ?? {}).reduce((p, v) => p + v[0], 0));
|
||||
const modifierSpent = computed(() => Object.values(data.value.modifiers ?? {}).reduce((p, v) => p + v, 0));
|
||||
const abilityPoints = computed(() => (selectedRaceOptions.value ? selectedRaceOptions.value.reduce((p, v) => p + (v?.abilities ?? 0), 0) : 0) + training.value.flatMap(e => e[1].filter(_e => _e.ability !== undefined)).reduce((p, v) => p + v.ability!, 0));
|
||||
const abilityMax = computed(() => Object.entries(characterConfig.abilities).reduce((p, v) => { p[v[0] as Ability] = abilitySpecialFeatures("max", data.value.training.curiosity, Math.floor(maxTraining.value[v[1].max[0]] / 3) + Math.floor(maxTraining.value[v[1].max[1]] / 3)); return p; }, {} as Record<Ability, number>));
|
||||
const abilitySpent = computed(() => Object.values(data.value.abilities ?? {}).reduce((p, v) => p + v[0], 0));
|
||||
const spellranks = computed(() => training.value.flatMap(e => e[1].filter(_e => _e.spellrank !== undefined)).reduce((p, v) => { p[v.spellrank!]++; return p; }, { instinct: 0, precision: 0, knowledge: 0 } as Record<SpellType, 0 | 1 | 2 | 3>));
|
||||
const spellsPoints = computed(() => training.value.flatMap(e => e[1].filter(_e => _e.spellslot !== undefined)).reduce((p, v) => p + (modifiers.value.hasOwnProperty(v.spellslot as MainStat) ? modifiers.value[v.spellslot as MainStat] : v.spellslot as number), 0));
|
||||
|
||||
if(id !== 'new')
|
||||
{
|
||||
@@ -98,34 +85,34 @@ if(id !== 'new')
|
||||
throw new Error('Donnée du personnage introuvables');
|
||||
}
|
||||
|
||||
data.value = { name: character.name, progress: character.progress } as Character;
|
||||
data.value = Object.assign(defaultCharacter, data.value, character);
|
||||
}
|
||||
|
||||
function selectRaceOption(level: Level, choice: number)
|
||||
{
|
||||
const character = data.value;
|
||||
if(level > character.progress.level)
|
||||
if(level > character.level)
|
||||
return;
|
||||
|
||||
if(character.progress.race.progress === undefined)
|
||||
character.progress.race.progress = [[1, 0]];
|
||||
if(character.leveling === undefined)
|
||||
character.leveling = [[1, 0]];
|
||||
|
||||
if(level == 1)
|
||||
return;
|
||||
|
||||
for(let i = 1; i < level; i++) //Check previous levels as a requirement
|
||||
{
|
||||
if(!character.progress.race.progress.some(e => e[0] == i))
|
||||
if(!character.leveling.some(e => e[0] == i))
|
||||
return;
|
||||
}
|
||||
|
||||
if(character.progress.race.progress.some(e => e[0] === level))
|
||||
if(character.leveling.some(e => e[0] == level))
|
||||
{
|
||||
character.progress.race.progress.splice(character.progress.race.progress.findIndex(e => e[0] === level), 1, [level, choice]);
|
||||
character.leveling.splice(character.leveling.findIndex(e => e[0] == level), 1, [level, choice]);
|
||||
}
|
||||
else
|
||||
{
|
||||
character.progress.race.progress.push([level, choice]);
|
||||
character.leveling.push([level, choice]);
|
||||
}
|
||||
|
||||
data.value = character;
|
||||
@@ -139,27 +126,27 @@ function switchTrainingOption(stat: MainStat, level: TrainingLevel, choice: numb
|
||||
|
||||
for(let i = 1; i < level; i++) //Check previous levels as a requirement
|
||||
{
|
||||
if(!character.progress.training[stat].some(e => e[0] == i))
|
||||
if(!character.training[stat].some(e => e[0] == i))
|
||||
return;
|
||||
}
|
||||
|
||||
if(character.progress.training[stat].some(e => e[0] === level))
|
||||
if(character.training[stat].some(e => e[0] == level))
|
||||
{
|
||||
if(character.progress.training[stat].some(e => e[0] === level && e[1] === choice))
|
||||
if(character.training[stat].some(e => e[0] == level && e[1] === choice))
|
||||
{
|
||||
for(let i = 15; i >= level; i --) //Invalidate higher levels
|
||||
{
|
||||
const index = character.progress.training[stat].findIndex(e => e[0] == i);
|
||||
const index = character.training[stat].findIndex(e => e[0] == i);
|
||||
if(index !== -1)
|
||||
character.progress.training[stat].splice(index, 1);
|
||||
character.training[stat].splice(index, 1);
|
||||
}
|
||||
}
|
||||
else
|
||||
character.progress.training[stat].splice(character.progress.training[stat].findIndex(e => e[0] === level), 1, [level, choice]);
|
||||
character.training[stat].splice(character.training[stat].findIndex(e => e[0] == level), 1, [level, choice]);
|
||||
}
|
||||
else if(trainingPoints.value && trainingPoints.value > 0)
|
||||
{
|
||||
character.progress.training[stat].push([level, choice]);
|
||||
character.training[stat].push([level, choice]);
|
||||
}
|
||||
|
||||
data.value = character;
|
||||
@@ -168,21 +155,34 @@ function updateLevel()
|
||||
{
|
||||
const character = data.value;
|
||||
|
||||
if(character.progress.race.progress) //Invalidate higher levels
|
||||
if(character.leveling) //Invalidate higher levels
|
||||
{
|
||||
for(let level = 20; level > character.progress.level; level--)
|
||||
for(let level = 20; level > character.level; level--)
|
||||
{
|
||||
const index = character.progress.race.progress.findIndex(e => e[0] == level);
|
||||
const index = character.leveling.findIndex(e => e[0] == level);
|
||||
if(index !== -1)
|
||||
character.progress.race.progress.splice(index, 1);
|
||||
character.leveling.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
data.value = character;
|
||||
}
|
||||
function filterSpells(spells: SpellConfig[])
|
||||
{
|
||||
const filter = spellFilter.value
|
||||
let list = [...spells];
|
||||
list = list.filter(e => spellranks.value[e.type] >= e.rank);
|
||||
if(filter.text.length > 0) list = list.filter(e => e.name.toLowerCase().includes(filter.text.toLowerCase()));
|
||||
if(filter.types.length > 0) list = list.filter(e => filter.types.includes(e.type));
|
||||
if(filter.ranks.length > 0) list = list.filter(e => filter.ranks.includes(e.rank));
|
||||
if(filter.elements.length > 0) list = list.filter(e => filter.elements.some(f => e.elements.includes(f)));
|
||||
if(filter.tags.length > 0) list = list.filter(e => !e.tags || filter.tags.some(f => e.tags!.includes(f)));
|
||||
|
||||
return list;
|
||||
}
|
||||
async function save(leave: boolean)
|
||||
{
|
||||
if(data.value.name === '' || data.value.progress.race.index === undefined || data.value.progress.race.index === -1)
|
||||
if(data.value.name === '' || data.value.people === undefined || data.value.people === -1)
|
||||
{
|
||||
add({ title: 'Données manquantes', content: "Merci de saisir un nom et une race avant de pouvoir enregistrer votre personnage", type: 'error', duration: 25000, timer: true });
|
||||
return;
|
||||
@@ -193,7 +193,7 @@ async function save(leave: boolean)
|
||||
method: 'post',
|
||||
body: data.value,
|
||||
onResponseError: (e) => {
|
||||
add({ title: 'Erreur d\enregistrement', content: e.response.status === 401 ? "Vous n'êtes pas autorisé à effectué cette opération" : e.response.statusText, type: 'error', duration: 25000, timer: true });
|
||||
add({ title: 'Erreur d\'enregistrement', content: e.response.status === 401 ? "Vous n'êtes pas autorisé à effectué cette opération" : e.response.statusText, type: 'error', duration: 25000, timer: true });
|
||||
}
|
||||
});
|
||||
add({ content: 'Personnage créé', type: 'success', duration: 25000, timer: true });
|
||||
@@ -206,7 +206,7 @@ async function save(leave: boolean)
|
||||
method: 'post',
|
||||
body: data.value,
|
||||
onResponseError: (e) => {
|
||||
add({ title: 'Erreur d\enregistrement', content: e.response.status === 401 ? "Vous n'êtes pas autorisé à effectué cette opération" : e.response.statusText, type: 'error', duration: 25000, timer: true });
|
||||
add({ title: 'Erreur d\'enregistrement', content: e.response.status === 401 ? "Vous n'êtes pas autorisé à effectué cette opération" : e.response.statusText, type: 'error', duration: 25000, timer: true });
|
||||
}
|
||||
});
|
||||
add({ content: 'Personnage enregistré', type: 'success', duration: 25000, timer: true });
|
||||
@@ -237,41 +237,46 @@ useShortcuts({
|
||||
</Label>
|
||||
<Label class="flex items-start justify-between flex-col gap-2">
|
||||
<span class="pb-1 mx-2 md:p-0">Niveau</span>
|
||||
<NumberFieldRoot :min="1" :max="20" v-model="data.progress.level" @update:model-value="updateLevel" class="flex justify-center border border-light-35 dark:border-dark-35 bg-light-20 dark:bg-dark-20 data-[disabled]:border-light-20 dark:data-[disabled]:border-dark-20 data-[disabled]:bg-light-20 dark:data-[disabled]:bg-dark-20
|
||||
<NumberFieldRoot :min="1" :max="20" v-model="data.level" @update:model-value="updateLevel" class="flex justify-center border border-light-35 dark:border-dark-35 bg-light-20 dark:bg-dark-20 data-[disabled]:border-light-20 dark:data-[disabled]:border-dark-20 data-[disabled]:bg-light-20 dark:data-[disabled]:bg-dark-20
|
||||
data-[disabled]:text-light-70 dark:data-[disabled]:text-dark-70 hover:border-light-50 dark:hover:border-dark-50 has-[:focus]:shadow-raw transition-[box-shadow] has-[:focus]:shadow-light-40 dark:has-[:focus]:shadow-dark-40">
|
||||
<NumberFieldInput class="tabular-nums w-20 bg-transparent px-3 py-1 outline-none caret-light-50 dark:caret-dark-50" />
|
||||
</NumberFieldRoot>
|
||||
</Label>
|
||||
<Label class="flex items-start justify-between flex-col gap-2">
|
||||
<span class="pb-1 mx-6 md:p-0">Visibilité</span>
|
||||
<Select class="!my-0" v-model="data.visibility">
|
||||
<SelectItem label="Privé" value="private" />
|
||||
<SelectItem label="Public" value="public" />
|
||||
</Select>
|
||||
</Label>
|
||||
</div>
|
||||
<div class="self-center">
|
||||
<Tooltip side="right" message="Ctrl+S"><Button @click="() => save(true)">Enregistrer</Button></Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-1 flex-col min-w-[800px] w-[75vw] max-w-[1200px]">
|
||||
<Collapsible class="border-b border-light-30 dark:border-dark-30 p-1" v-model="peopleOpen" @update:model-value="() => { trainingOpen = false; abilityOpen = false; notesOpen = false; }">
|
||||
<Collapsible class="border-b border-light-30 dark:border-dark-30 p-1" v-model="peopleOpen" @update:model-value="() => { trainingOpen = false; abilityOpen = false; spellOpen = false; notesOpen = false; }">
|
||||
<template #label>
|
||||
<span class="font-bold text-xl">Peuple</span>
|
||||
</template>
|
||||
<template #default>
|
||||
<div class="m-2 overflow-auto">
|
||||
<Select label="Peuple de votre personnage" :v-model="data.progress.race.index" :default-value="data.progress.race.index?.toString() ?? ''" @update:model-value="(index) => { data.progress.race.index = parseInt(index ?? '-1'); data.progress.race.progress = [[1, 0]]}">
|
||||
<SelectItem v-for="(people, index) of characterConfig.peoples" :label="people.name" :value="index.toString()" :key="index" />
|
||||
</Select>
|
||||
<template v-if="data.progress.race.index !== undefined">
|
||||
<Combobox label="Peuple de votre personnage" v-model="data.people" :options="config.peoples.map((people, index) => [people.name, index])" @update:model-value="(index) => { data.people = index as number | undefined; data.leveling = [[1, 0]]}" />
|
||||
<template v-if="data.people !== undefined">
|
||||
<div class="w-full border-b border-light-30 dark:border-dark-30 pb-4">
|
||||
<span class="text-sm text-light-70 dark:text-dark-70">{{ characterConfig.peoples[data.progress.race.index].description }}</span>
|
||||
<span class="text-sm text-light-70 dark:text-dark-70">{{ characterConfig.peoples[data.people].description }}</span>
|
||||
</div>
|
||||
<div class="flex flex-col gap-4 max-h-[50vh] pe-4 relative">
|
||||
<span class="sticky top-0 py-1 bg-light-0 dark:bg-dark-0 z-10 text-xl">Niveaux restants: {{ data.progress.level - (data.progress.race.progress?.length ?? 0) }}</span>
|
||||
<div class="flex flex-row gap-4 justify-center" v-for="(level, index) of characterConfig.peoples[data.progress.race.index].options" :class="{ 'opacity-30': index > data.progress.level }">
|
||||
<div class="border border-light-40 dark:border-dark-40 cursor-pointer px-2 py-1 w-64" v-for="(option, i) of level" @click="selectRaceOption(index as Level, i)" :class="{ 'hover:border-light-60 dark:hover:border-dark-60': index <= data.progress.level, '!border-accent-blue bg-accent-blue bg-opacity-20': data.progress.race.progress?.some(e => e[0] == index && e[1] === i) ?? false }"><MarkdownRenderer :content="raceOptionToText(option)" /></div>
|
||||
<span class="sticky top-0 py-1 bg-light-0 dark:bg-dark-0 z-10 text-xl">Niveaux restants: {{ data.level - (data.leveling?.length ?? 0) }}</span>
|
||||
<div class="flex flex-row gap-4 justify-center" v-for="(level, index) of characterConfig.peoples[data.people].options" :class="{ 'opacity-30': index > data.level }">
|
||||
<div class="border border-light-40 dark:border-dark-40 cursor-pointer px-2 py-1 w-64" v-for="(option, i) of level" @click="selectRaceOption(parseInt(index as unknown as string, 10) as Level, i)" :class="{ 'hover:border-light-60 dark:hover:border-dark-60': index <= data.level, '!border-accent-blue bg-accent-blue bg-opacity-20': data.leveling?.some(e => e[0] == index && e[1] === i) ?? false }"><MarkdownRenderer :content="raceOptionToText(option)" /></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</Collapsible>
|
||||
<Collapsible class="border-b border-light-30 dark:border-dark-30 p-1" v-model="trainingOpen" :disabled="data.progress.race.index === undefined" @update:model-value="() => { peopleOpen = false; abilityOpen = false; notesOpen = false; }">
|
||||
<Collapsible class="border-b border-light-30 dark:border-dark-30 p-1" v-model="trainingOpen" :disabled="data.people === undefined" @update:model-value="() => { peopleOpen = false; abilityOpen = false; spellOpen = false; notesOpen = false; }">
|
||||
<template #label>
|
||||
<span class="font-bold text-xl">Entrainement</span>
|
||||
</template>
|
||||
@@ -287,7 +292,7 @@ useShortcuts({
|
||||
<div class="sticky top-1 mx-16 z-10 flex justify-between">
|
||||
<div class="py-1 px-3 bg-light-0 dark:bg-dark-0 z-10 text-xl font-bold border border-light-30 dark:border-dark-30 flex">{{ text }}
|
||||
<div class="flex gap-2" v-if="maxTraining[stat] >= 0">: Niveau {{ maxTraining[stat] }} (+{{ modifiers[stat] }}
|
||||
<NumberFieldRoot :default-value="data.progress.modifiers[stat] ?? 0" v-model="data.progress.modifiers[stat]" class="flex justify-center border border-light-35 dark:border-dark-35 bg-light-20 dark:bg-dark-20 data-[disabled]:border-light-20 dark:data-[disabled]:border-dark-20 data-[disabled]:bg-light-20 dark:data-[disabled]:bg-dark-20
|
||||
<NumberFieldRoot :default-value="data.modifiers[stat] ?? 0" v-model="data.modifiers[stat]" class="flex justify-center border border-light-35 dark:border-dark-35 bg-light-20 dark:bg-dark-20 data-[disabled]:border-light-20 dark:data-[disabled]:border-dark-20 data-[disabled]:bg-light-20 dark:data-[disabled]:bg-dark-20
|
||||
data-[disabled]:text-light-70 dark:data-[disabled]:text-dark-70 hover:border-light-50 dark:hover:border-dark-50 has-[:focus]:shadow-raw transition-[box-shadow] has-[:focus]:shadow-light-40 dark:has-[:focus]:shadow-dark-40">
|
||||
<NumberFieldInput class="tabular-nums w-8 text-base font-normal bg-transparent px-2 outline-none caret-light-50 dark:caret-dark-50" />
|
||||
</NumberFieldRoot>
|
||||
@@ -295,14 +300,14 @@ useShortcuts({
|
||||
<div class="py-1 px-3 bg-light-0 dark:bg-dark-0 z-10 flex gap-2 justify-center items-center" :class="{ 'text-light-red dark:text-dark-red': (modifierPoints ?? 0) < modifierSpent }">Modifieur bonus: {{ modifierPoints - modifierSpent }}</div>
|
||||
</div>
|
||||
<div class="flex flex-row gap-4 justify-center" v-for="(level, index) of characterConfig.training[stat]" :class="{ 'opacity-30': index > maxTraining[stat] + 1 }">
|
||||
<div class="border border-light-40 dark:border-dark-40 cursor-pointer px-2 py-1 w-1/3" v-for="(option, i) of level" @click="switchTrainingOption(stat, index as TrainingLevel, i)" :class="{ 'hover:border-light-60 dark:hover:border-dark-60': index <= maxTraining[stat] + 1, '!border-accent-blue bg-accent-blue bg-opacity-20': index == 0 || (data.progress.training[stat]?.some(e => e[0] == index && e[1] === i) ?? false) }"><MarkdownRenderer :proses="{ 'a': PreviewA }" :content="option.description.map(e => e.text).join('\n')" /></div>
|
||||
<div class="border border-light-40 dark:border-dark-40 cursor-pointer px-2 py-1 w-1/3" v-for="(option, i) of level" @click="switchTrainingOption(stat, parseInt(index as unknown as string, 10) as TrainingLevel, i)" :class="{ 'hover:border-light-60 dark:hover:border-dark-60': index <= maxTraining[stat] + 1, '!border-accent-blue bg-accent-blue bg-opacity-20': index == 0 || (data.training[stat]?.some(e => e[0] == index && e[1] === i) ?? false) }"><MarkdownRenderer :proses="{ 'a': PreviewA }" :content="option.description.map(e => e.text).join('\n')" /></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Collapsible>
|
||||
<Collapsible class="border-b border-light-30 dark:border-dark-30 p-1" v-model="abilityOpen" :disabled="data.progress.race.index === undefined" @update:model-value="() => { trainingOpen = false;peopleOpen = false; notesOpen = false; }">
|
||||
<Collapsible class="border-b border-light-30 dark:border-dark-30 p-1" v-model="abilityOpen" :disabled="data.people === undefined" @update:model-value="() => { trainingOpen = false; peopleOpen = false; spellOpen = false; notesOpen = false; }">
|
||||
<template #label>
|
||||
<span class="font-bold text-xl">Compétences</span>
|
||||
</template>
|
||||
@@ -314,7 +319,7 @@ useShortcuts({
|
||||
<div class="grid gap-4 grid-cols-6">
|
||||
<div v-for="(ability, index) of characterConfig.abilities" class="flex flex-col items-center border border-light-30 dark:border-dark-30 p-2">
|
||||
<div class="flex items-center justify-center gap-4">
|
||||
<NumberFieldRoot :min="0" :default-value="data.progress.abilities[index] ? data.progress.abilities[index][0] : 0" @update:model-value="(value) => { data.progress.abilities[index] = [value, data.progress.abilities[index] ? data.progress.abilities[index][1] : 0]; }" class="border border-light-35 dark:border-dark-35 bg-light-20 dark:bg-dark-20 data-[disabled]:border-light-20 dark:data-[disabled]:border-dark-20 data-[disabled]:bg-light-20 dark:data-[disabled]:bg-dark-20
|
||||
<NumberFieldRoot :min="0" :default-value="data.abilities[index] ? data.abilities[index][0] : 0" @update:model-value="(value) => { data.abilities[index] = [value, data.abilities[index] ? data.abilities[index][1] : 0]; }" class="border border-light-35 dark:border-dark-35 bg-light-20 dark:bg-dark-20 data-[disabled]:border-light-20 dark:data-[disabled]:border-dark-20 data-[disabled]:bg-light-20 dark:data-[disabled]:bg-dark-20
|
||||
data-[disabled]:text-light-70 dark:data-[disabled]:text-dark-70 hover:border-light-50 dark:hover:border-dark-50 has-[:focus]:shadow-raw transition-[box-shadow] has-[:focus]:shadow-light-40 dark:has-[:focus]:shadow-dark-40">
|
||||
<NumberFieldInput class="tabular-nums w-8 bg-transparent px-3 py-1 outline-none caret-light-50 dark:caret-dark-50" />
|
||||
</NumberFieldRoot>
|
||||
@@ -327,12 +332,48 @@ useShortcuts({
|
||||
</div>
|
||||
</template>
|
||||
</Collapsible>
|
||||
<Collapsible class="border-b border-light-30 dark:border-dark-30 p-1" v-model="notesOpen" @update:model-value="() => { trainingOpen = false; peopleOpen = false; abilityOpen = false; }">
|
||||
<Collapsible class="border-b border-light-30 dark:border-dark-30 p-1" v-model="spellOpen" :disabled="data.people === undefined" @update:model-value="() => { trainingOpen = false; peopleOpen = false; abilityOpen = false; notesOpen = false; }">
|
||||
<template #label>
|
||||
<span class="font-bold text-xl">Sorts</span>
|
||||
</template>
|
||||
<template #default>
|
||||
<div class="flex flex-col gap-2 max-h-[50vh] px-4 relative overflow-y-auto">
|
||||
<div class="sticky top-0 py-2 bg-light-0 dark:bg-dark-0 z-10 flex gap-2 items-center">
|
||||
<span class="text-xl pe-4" :class="{ 'text-light-red dark:text-dark-red': spellsPoints < (data.spells?.length ?? 0) }">Sorts: {{ data.spells?.length ?? 0 }}/{{ spellsPoints }}</span>
|
||||
<TextInput label="Nom" v-model="spellFilter.text" />
|
||||
<Combobox label="Rang" v-model="spellFilter.ranks" multiple :options="[['Rang 1', 1], ['Rang 2', 2], ['Rang 3', 3]]" />
|
||||
<Combobox label="Type" v-model="spellFilter.types" multiple :options="[['Précision', 'precision'], ['Savoir', 'knowledge'], ['Instinct', 'instinct']]" />
|
||||
<Combobox label="Element" v-model="spellFilter.elements" multiple :options="[['Feu', 'fire'], ['Glace', 'ice'], ['Foudre', 'thunder'], ['Terre', 'earth'], ['Arcane', 'arcana'], ['Air', 'air'], ['Nature', 'nature'], ['Lumière', 'light'], ['Psy', 'psyche']]" />
|
||||
</div>
|
||||
<div class="grid gap-4 grid-cols-2">
|
||||
<div class="py-1 px-2 border border-light-30 dark:border-dark-30 flex flex-col hover:border-light-50 dark:hover:border-dark-50 cursor-pointer" v-for="spell of filterSpells(characterConfig.spells)" :class="{ '!border-accent-blue bg-accent-blue bg-opacity-20': data.spells?.find(e => e === spell.id) }"
|
||||
@click="() => data.spells?.includes(spell.id) ? data.spells.splice(data.spells.findIndex((e: string) => e === spell.id), 1) : data.spells!.push(spell.id)">
|
||||
<div class="flex flex-row justify-between">
|
||||
<span class="text-lg font-bold">{{ spell.name }}</span>
|
||||
<div class="flex flex-row items-center gap-6">
|
||||
<div class="flex flex-row text-sm gap-2">
|
||||
<span v-for="element of spell.elements" :class="elementTexts[element].class">{{ elementTexts[element].text }}</span>
|
||||
</div>
|
||||
<div class="flex flex-row text-sm gap-1">
|
||||
<span class="">Rang {{ spell.rank }}</span><span>/</span>
|
||||
<span class="">{{ spellTypeTexts[spell.type] }}</span><span>/</span>
|
||||
<span class="">{{ spell.cost }} mana</span><span>/</span>
|
||||
<span class="">{{ typeof spell.speed === 'string' ? spell.speed : `${spell.speed} minutes` }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<MarkdownRenderer :content="spell.effect" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Collapsible>
|
||||
<Collapsible class="border-b border-light-30 dark:border-dark-30 p-1" v-model="notesOpen" @update:model-value="() => { trainingOpen = false; peopleOpen = false; abilityOpen = false; spellOpen = false; }">
|
||||
<template #label>
|
||||
<span class="font-bold text-xl">Notes libres</span>
|
||||
</template>
|
||||
<template #default>
|
||||
<Editor class="min-h-[400px] border border-light-30 dark:border-dark-30" v-model="data.progress.notes" />
|
||||
<Editor class="min-h-[400px] border border-light-30 dark:border-dark-30" v-model="data.notes" />
|
||||
</template>
|
||||
</Collapsible>
|
||||
</div>
|
||||
|
||||
@@ -2,10 +2,16 @@
|
||||
import config from '#shared/character-config.json';
|
||||
import { Icon } from '@iconify/vue/dist/iconify.js';
|
||||
import PreviewA from '~/components/prose/PreviewA.vue';
|
||||
import type { SpellConfig } from '~/types/character';
|
||||
import { elementTexts, spellTypeTexts, type CharacterConfig } from '~/types/character';
|
||||
|
||||
const characterConfig = config as CharacterConfig;
|
||||
|
||||
const id = useRouter().currentRoute.value.params.id;
|
||||
const { user } = useUserSession();
|
||||
const { data: character, status, error } = await useAsyncData(() => useRequestFetch()(`/api/character/${id}/compiled`));
|
||||
const { add } = useToast();
|
||||
|
||||
const { data: character, status, error } = await useFetch(`/api/character/${id}/compiled`);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -20,43 +26,46 @@ const { data: character, status, error } = await useAsyncData(() => useRequestFe
|
||||
</Head>
|
||||
<div class="flex flex-row gap-4 justify-between">
|
||||
<div></div>
|
||||
<div class="flex flex-row gap-6 items-center justify-center">
|
||||
<Avatar src="" icon="radix-icons:person" size="large" />
|
||||
<div class="flex flex-col">
|
||||
<span class="text-xl font-bold">{{ character.name }}</span>
|
||||
<span class="text-sm">De {{ character.username }}</span>
|
||||
<div class="flex lg:flex-row flex-col gap-6 items-center justify-center">
|
||||
<div class="flex gap-6 items-center">
|
||||
<Avatar src="" icon="radix-icons:person" size="large" />
|
||||
<div class="flex flex-col">
|
||||
<span class="text-xl font-bold">{{ character.name }}</span>
|
||||
<span class="text-sm">De {{ character.username }}</span>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<span class="font-bold">Niveau {{ character.level }}</span>
|
||||
<span>{{ character.race === -1 ? "Race inconnue" : characterConfig.peoples[character.race].name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<span class="font-bold">Niveau {{ character.level }}</span>
|
||||
<span>{{ character.race === -1 ? "Race inconnue" : config.peoples[character.race].name }}</span>
|
||||
<div class="flex gap-6 lg:border-l border-light-30 dark:border-dark-30 py-4 ps-4">
|
||||
<span class="flex flex-row items-center gap-2">PV: {{ character.health - character.values.hp }}/{{ character.health }}</span>
|
||||
<span class="flex flex-row items-center gap-2">Mana: {{ character.mana - character.values.mana }}/{{ character.mana }}</span>
|
||||
</div>
|
||||
<span class="h-full border-l border-light-30 dark:border-dark-30"></span>
|
||||
<span>PV: {{ character.health }}</span>
|
||||
<span>Mana: {{ character.mana }}</span>
|
||||
</div>
|
||||
<div class="self-center">
|
||||
<Tooltip side="right" message="Modifier" v-if="user && user.id === character.owner"><NuxtLink :to="{ name: 'character-id-edit', params: { id: character.id } }"><Button icon><Icon icon="radix-icons:pencil-2" /></Button></NuxtLink></Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-1 flex-col justify-center gap-4 *:py-2">
|
||||
<div class="flex flex-row gap-4 items-center justify-center border-b border-light-30 dark:border-dark-30">
|
||||
<div class="flex relative ps-4">
|
||||
<div class="flex flex-col px-2 items-center"><span class="text-2xl font-bold">+{{ character.modifier.strength }}</span><span>Force</span></div>
|
||||
<div class="flex flex-col px-2 items-center"><span class="text-2xl font-bold">+{{ character.modifier.dexterity }}</span><span>Dextérité</span></div>
|
||||
<div class="flex flex-col px-2 items-center"><span class="text-2xl font-bold">+{{ character.modifier.constitution }}</span><span>Constitution</span></div>
|
||||
<div class="flex flex-col px-2 items-center"><span class="text-2xl font-bold">+{{ character.modifier.intelligence }}</span><span>Intelligence</span></div>
|
||||
<div class="flex flex-col px-2 items-center"><span class="text-2xl font-bold">+{{ character.modifier.curiosity }}</span><span>Curiosité</span></div>
|
||||
<div class="flex flex-col px-2 items-center"><span class="text-2xl font-bold">+{{ character.modifier.charisma }}</span><span>Charisme</span></div>
|
||||
<div class="flex flex-col px-2 items-center"><span class="text-2xl font-bold">+{{ character.modifier.psyche }}</span><span>Psyché</span></div>
|
||||
<div class="grid 2xl:grid-cols-12 grid-cols-2 gap-4 items-center border-b border-light-30 dark:border-dark-30">
|
||||
<div class="flex relative justify-between ps-4 gap-2 2xl:col-span-6 lg:col-span-2">
|
||||
<div class="flex flex-col items-center"><span class="2xl:text-2xl text-xl font-bold">+{{ character.modifier.strength }}</span><span class="text-sm 2xl:text-base">Force</span></div>
|
||||
<div class="flex flex-col items-center"><span class="2xl:text-2xl text-xl font-bold">+{{ character.modifier.dexterity }}</span><span class="text-sm 2xl:text-base">Dextérité</span></div>
|
||||
<div class="flex flex-col items-center"><span class="2xl:text-2xl text-xl font-bold">+{{ character.modifier.constitution }}</span><span class="text-sm 2xl:text-base">Constitution</span></div>
|
||||
<div class="flex flex-col items-center"><span class="2xl:text-2xl text-xl font-bold">+{{ character.modifier.intelligence }}</span><span class="text-sm 2xl:text-base">Intelligence</span></div>
|
||||
<div class="flex flex-col items-center"><span class="2xl:text-2xl text-xl font-bold">+{{ character.modifier.curiosity }}</span><span class="text-sm 2xl:text-base">Curiosité</span></div>
|
||||
<div class="flex flex-col items-center"><span class="2xl:text-2xl text-xl font-bold">+{{ character.modifier.charisma }}</span><span class="text-sm 2xl:text-base">Charisme</span></div>
|
||||
<div class="flex flex-col items-center"><span class="2xl:text-2xl text-xl font-bold">+{{ character.modifier.psyche }}</span><span class="text-sm 2xl:text-base">Psyché</span></div>
|
||||
</div>
|
||||
<div class="flex relative border-l border-light-30 dark:border-dark-30 ps-4">
|
||||
<div class="flex relative 2xl:border-l border-light-30 dark:border-dark-30 ps-4 2xl:col-span-2">
|
||||
<div class="flex flex-1 flex-row items-center justify-between">
|
||||
<div class="flex flex-col px-2 items-center"><span class="text-2xl font-bold">+{{ character.initiative }}</span><span>Initiative</span></div>
|
||||
<div class="flex flex-col px-2 items-center"><span class="text-2xl font-bold">{{ character.speed === false ? "Aucun déplacement" : `${character.speed} cases` }}</span><span>Course</span></div>
|
||||
</div>
|
||||
<!-- <div class="absolute top-0 left-0 bottom-0 right-0 bg-light-0 dark:bg-dark-0 bg-opacity-50 dark:bg-opacity-50 text-xl font-bold flex items-center justify-center">Les données secondaires arrivent bientôt.</div> -->
|
||||
</div>
|
||||
<div class="flex relative border-l border-light-30 dark:border-dark-30 ps-4">
|
||||
<div class="flex relative border-l border-light-30 dark:border-dark-30 ps-4 2xl:col-span-4">
|
||||
<div class="flex flex-col px-2">
|
||||
<span class="text-xl">Défense passive: <span class="text-2xl font-bold">{{ character.defense.static }}</span>/+<span class="text-2xl font-bold">{{ character.defense.passivedodge }}</span>/+<span class="text-2xl font-bold">{{ character.defense.passiveparry }}</span></span>
|
||||
<span class="text-xl">Défense active: <span class="float-right">+<span class="text-2xl font-bold">{{ character.defense.activedodge }}</span>/+<span class="text-2xl font-bold">{{ character.defense.activeparry }}</span></span></span>
|
||||
@@ -64,7 +73,7 @@ const { data: character, status, error } = await useAsyncData(() => useRequestFe
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-1 px-8">
|
||||
<div class="flex flex-col pe-8 gap-4 py-8 w-80">
|
||||
<div class="flex flex-col pe-8 gap-4 py-8 w-80 border-r border-light-30 dark:border-dark-30">
|
||||
<div class="flex flex-col">
|
||||
<span class="text-lg font-semibold border-b border-light-30 dark:border-dark-30">Maitrise d'arme</span>
|
||||
<div class="grid grid-cols-2 gap-x-3 gap-y-1">
|
||||
@@ -99,43 +108,77 @@ const { data: character, status, error } = await useAsyncData(() => useRequestFe
|
||||
<div class="flex flex-col">
|
||||
<span class="text-lg font-semibold border-b border-light-30 dark:border-dark-30 mb-2 flex items-center gap-4">Résistances (Attaque/Défense) <Tooltip side="right" message="Les défenses affichées incluent déjà leur modifieur de statistique."><Icon icon="radix-icons:question-mark-circled" /></Tooltip></span>
|
||||
<div class="grid grid-cols-3 gap-1">
|
||||
<div class="flex flex-col px-2 items-center text-sm text-light-70 dark:text-dark-70" v-for="(value, resistance) of character.resistance"><span class="font-bold text-base text-light-100 dark:text-dark-100">+{{ value[0] }}/+{{ value[1] + character.modifier[config.resistances[resistance].statistic as MainStat] }}</span><span>{{ config.resistances[resistance].name }}</span></div>
|
||||
<div class="flex flex-col px-2 items-center text-sm text-light-70 dark:text-dark-70" v-for="(value, resistance) of character.resistance"><span class="font-bold text-base text-light-100 dark:text-dark-100">+{{ value[0] }}/+{{ value[1] + character.modifier[characterConfig.resistances[resistance].statistic as MainStat] }}</span><span>{{ characterConfig.resistances[resistance].name }}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<span class="text-lg font-semibold border-b border-light-30 dark:border-dark-30 mb-2">Compétences</span>
|
||||
<div class="grid grid-cols-3 gap-1">
|
||||
<div class="flex flex-col px-2 items-center text-sm text-light-70 dark:text-dark-70" v-for="(value, ability) of character.abilities"><span class="font-bold text-base text-light-100 dark:text-dark-100">+{{ value }}</span><span>{{ config.abilities[ability].name }}</span></div>
|
||||
<div class="flex flex-col px-2 items-center text-sm text-light-70 dark:text-dark-70" v-for="(value, ability) of character.abilities"><span class="font-bold text-base text-light-100 dark:text-dark-100">+{{ value }}</span><span>{{ characterConfig.abilities[ability].name }}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-1 flex-col border-l border-light-30 dark:border-dark-30 ps-8 gap-4 py-8 max-w-[80rem]">
|
||||
<div class="grid grid-cols-3 gap-2">
|
||||
<div class="flex flex-col">
|
||||
<span class="text-lg font-semibold">Actions</span>
|
||||
<span class="text-sm text-light-70 dark:text-dark-70">Attaquer - Saisir - Faire chuter - Déplacer - Courir - Pas de coté - Lancer un sort - S'interposer - Se transformer - Utiliser un objet - Anticiper une action - Improviser</span>
|
||||
<MarkdownRenderer :content="character.features.action.join('\n')" />
|
||||
<TabsRoot default-value="features" class="w-[60rem]">
|
||||
<TabsList class="flex flex-row gap-4 relative px-4">
|
||||
<TabsIndicator class="absolute px-8 left-0 h-[3px] bottom-0 w-[--radix-tabs-indicator-size] translate-x-[--radix-tabs-indicator-position] transition-[width,transform] duration-300 bg-accent-blue"></TabsIndicator>
|
||||
<TabsTrigger value="features" class="px-2 py-1 border-b border-transparent hover:border-accent-blue">Aptitudes</TabsTrigger>
|
||||
<TabsTrigger value="spells" class="px-2 py-1 border-b border-transparent hover:border-accent-blue">Sorts</TabsTrigger>
|
||||
<TabsTrigger value="notes" class="px-2 py-1 border-b border-transparent hover:border-accent-blue">Notes</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="features">
|
||||
<div class="flex flex-1 flex-col ps-8 gap-4 py-8">
|
||||
<div class="grid grid-cols-3 gap-2">
|
||||
<div class="flex flex-col">
|
||||
<span class="text-lg font-semibold">Actions</span>
|
||||
<span class="text-sm text-light-70 dark:text-dark-70">Attaquer - Saisir - Faire chuter - Déplacer - Courir - Pas de coté - Lancer un sort - S'interposer - Se transformer - Utiliser un objet - Anticiper une action - Improviser</span>
|
||||
<MarkdownRenderer :content="character.features.action.join('\n')" />
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<span class="text-lg font-semibold">Réactions</span>
|
||||
<span class="text-sm text-light-70 dark:text-dark-70">Parade - Esquive - Saisir une opportunité - Prendre en tenaille - Intercepter - Désarmer</span>
|
||||
<MarkdownRenderer :content="character.features.reaction.join('\n')" />
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<span class="text-lg font-semibold">Actions libre</span>
|
||||
<span class="text-sm text-light-70 dark:text-dark-70">Analyser une situation - Communiquer</span>
|
||||
<MarkdownRenderer :content="character.features.freeaction.join('\n')" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<span class="text-lg font-semibold">Aptitudes</span>
|
||||
<MarkdownRenderer :content="character.features.misc.map(e => `> ${e}`).join('\n\n')" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<span class="text-lg font-semibold">Réactions</span>
|
||||
<span class="text-sm text-light-70 dark:text-dark-70">Parade - Esquive - Saisir une opportunité - Prendre en tenaille - Intercepter - Désarmer</span>
|
||||
<MarkdownRenderer :content="character.features.reaction.join('\n')" />
|
||||
</TabsContent>
|
||||
<TabsContent v-if="character.spells.length > 0" value="spells">
|
||||
<div class="flex flex-1 flex-col ps-8 gap-4 py-8">
|
||||
<div class="flex flex-col">
|
||||
<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.spells.map(e => characterConfig.spells.find((f: SpellConfig) => f.id === e)).filter(e => !!e)">
|
||||
<div class="flex flex-row justify-between">
|
||||
<span class="text-lg font-bold">{{ spell.name }}</span>
|
||||
<div class="flex flex-row items-center gap-6">
|
||||
<div class="flex flex-row text-sm gap-2">
|
||||
<span v-for="element of spell.elements" :class="elementTexts[element].class">{{ elementTexts[element].text }}</span>
|
||||
</div>
|
||||
<div class="flex flex-row text-sm gap-1">
|
||||
<span class="">Rang {{ spell.rank }}</span><span>/</span>
|
||||
<span class="">{{ spellTypeTexts[spell.type] }}</span><span>/</span>
|
||||
<span class="">{{ spell.cost }} mana</span><span>/</span>
|
||||
<span class="">{{ typeof spell.speed === 'string' ? spell.speed : `${spell.speed} minutes` }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<MarkdownRenderer :content="spell.effect" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<span class="text-lg font-semibold">Actions libre</span>
|
||||
<span class="text-sm text-light-70 dark:text-dark-70">Analyser une situation - Communiquer</span>
|
||||
<MarkdownRenderer :content="character.features.freeaction.join('\n')" />
|
||||
</TabsContent>
|
||||
<TabsContent value="notes">
|
||||
<div class="flex flex-1 flex-col ps-8 gap-4 py-8">
|
||||
<MarkdownRenderer :content="character.notes" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<span class="text-lg font-semibold">Aptitudes</span>
|
||||
<MarkdownRenderer :content="character.features.misc.map(e => `> ${e}`).join('\n\n')" />
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<span class="text-lg font-semibold border-b border-light-30 dark:border-dark-30">Notes</span>
|
||||
<MarkdownRenderer :content="character.notes" />
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
</TabsRoot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,24 +1,30 @@
|
||||
<script setup lang="ts">
|
||||
import type { Progression } from '~/types/character';
|
||||
import { Icon } from '@iconify/vue/dist/iconify.js';
|
||||
|
||||
definePageMeta({
|
||||
guestsGoesTo: '/user/login',
|
||||
})
|
||||
const { add } = useToast();
|
||||
const { user } = useUserSession();
|
||||
const loading = ref(true);
|
||||
const characters = ref<Array<{ id: number, name: string, progress: Progression }>>([]);
|
||||
characters.value = await useRequestFetch()('/api/character');
|
||||
loading.value = false;
|
||||
|
||||
const { data: characters, error, status } = await useFetch(`/api/character`);
|
||||
|
||||
async function deleteCharacter(id: number)
|
||||
{
|
||||
loading.value = true;
|
||||
status.value = "pending";
|
||||
await useRequestFetch()(`/api/character/${id}`, { method: 'delete' });
|
||||
loading.value = false;
|
||||
status.value = "success";
|
||||
add({ content: 'Personnage supprimé', type: 'info', duration: 25000, timer: true, });
|
||||
characters.value = characters.value?.filter(e => e.id !== id);
|
||||
}
|
||||
async function duplicateCharacter(id: number)
|
||||
{
|
||||
status.value = "pending";
|
||||
const newId = await useRequestFetch()(`/api/character/${id}/duplicate`, { method: 'post' });
|
||||
status.value = "success";
|
||||
add({ content: 'Personnage dupliqué', type: 'info', duration: 25000, timer: true, });
|
||||
useRouter().push({ name: 'character-id', params: { id: newId } });
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -26,36 +32,64 @@ async function deleteCharacter(id: number)
|
||||
<Title>d[any] - Mes personnages</Title>
|
||||
</Head>
|
||||
<div class="flex flex-col">
|
||||
<NuxtLink v-if="user?.state === 1" :to="{ name: 'character-id-edit', params: { id: 'new' } }" class="flex align-center justify-center"><Button>Nouveau personnage</Button></NuxtLink>
|
||||
<Tooltip v-else side="top" message="Veuillez valider votre email avant de pouvoir créer un personnage."><Button disabled>Nouveau personnage</Button></Tooltip>
|
||||
<div v-if="loading" class="flex flex-1 justify-center align-center">
|
||||
<div class="flex align-center justify-center">
|
||||
<NuxtLink v-if="user?.state === 1" :to="{ name: 'character-id-edit', params: { id: 'new' } }"><Button>Nouveau personnage</Button></NuxtLink>
|
||||
<Tooltip v-else side="top" message="Veuillez valider votre email avant de pouvoir créer un personnage."><Button disabled>Nouveau personnage</Button></Tooltip>
|
||||
</div>
|
||||
<div v-if="status === 'pending'" class="flex flex-1 justify-center align-center">
|
||||
<Loading size="large" />
|
||||
</div>
|
||||
<div v-else class="grid p-6 grid-cols-4 gap-4">
|
||||
<div class="border border-light-30 dark:border-dark-30 p-1 flex flex-row gap-4" v-for="character of characters">
|
||||
<Avatar size="large" icon="radix-icons:person" src="" class="m-2" />
|
||||
<div class="flex flex-col justify-between w-64">
|
||||
<NuxtLink class="flex-1 text-xl font-bold hover:text-accent-blue truncate" :to="{ name: 'character-id', params: { id: character.id } }" :title="character.name">{{ character.name }}</NuxtLink>
|
||||
<span class="flex-1 text-sm truncate">Niveau {{ character.progress.level }}</span>
|
||||
<div class="flex flex-row gap-8">
|
||||
<NuxtLink class="font-bold text-accent-blue hover:text-opacity-50" :to="{ name: 'character-id-edit', params: { id: character.id } }">Editer</NuxtLink>
|
||||
<AlertDialogRoot>
|
||||
<AlertDialogTrigger asChild><span class="font-bold text-light-red dark:text-dark-red hover:text-opacity-50 cursor-pointer">Supprimer</span></AlertDialogTrigger>
|
||||
<AlertDialogPortal>
|
||||
<AlertDialogOverlay class="bg-light-0 dark:bg-dark-0 opacity-70 fixed inset-0 z-40" />
|
||||
<AlertDialogContent
|
||||
class="data-[state=open]:animate-contentShow fixed top-[50%] left-[50%] max-h-[85vh] w-[90vw] max-w-[800px] translate-x-[-50%] translate-y-[-50%] bg-light-10 dark:bg-dark-10 border border-light-30 dark:border-dark-30 p-6 z-50 text-light-100 dark:text-dark-100">
|
||||
<AlertDialogTitle class="text-3xl font-light relative -top-2">Supprimer {{ character.name }} ?</AlertDialogTitle>
|
||||
<div class="flex flex-1 justify-end gap-4">
|
||||
<AlertDialogCancel asChild><Button>Non</Button></AlertDialogCancel>
|
||||
<AlertDialogAction asChild><Button @click="() => deleteCharacter(character.id)" class="border-light-red dark:border-dark-red hover:border-light-red dark:hover:border-dark-red hover:bg-light-redBack dark:hover:bg-dark-redBack text-light-red dark:text-dark-red focus:shadow-light-red dark:focus:shadow-dark-red">Oui</Button></AlertDialogAction>
|
||||
</div>
|
||||
</AlertDialogContent>
|
||||
</AlertDialogPortal>
|
||||
</AlertDialogRoot>
|
||||
</div>
|
||||
<div v-else-if="status === 'success'" class="grid p-6 2xl:grid-cols-3 lg:grid-cols-2 grid-cols-1 gap-4 w-full">
|
||||
<div class="border border-light-30 dark:border-dark-30 p-3 flex flex-row gap-4" v-for="character of characters">
|
||||
<Avatar size="large" icon="radix-icons:person" src="" />
|
||||
<div class="flex flex-1 flex-shrink flex-col truncate">
|
||||
<NuxtLink class="text-xl font-bold hover:text-accent-blue truncate" :to="{ name: 'character-id', params: { id: character.id } }" :title="character.name">{{ character.name }}</NuxtLink>
|
||||
<span class="text-sm truncate">Niveau {{ character.level }}</span>
|
||||
</div>
|
||||
<AlertDialogRoot>
|
||||
<DropdownMenuRoot>
|
||||
<DropdownMenuTrigger class="self-start">
|
||||
<Button icon><Icon icon="radix-icons:dots-vertical" /></Button>
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
<DropdownMenuPortal>
|
||||
<DropdownMenuContent align="end" side="bottom" class="z-50 outline-none bg-light-20 dark:bg-dark-20 will-change-[opacity,transform] data-[side=top]:animate-slideDownAndFade data-[side=right]:animate-slideLeftAndFade data-[side=bottom]:animate-slideUpAndFade data-[side=left]:animate-slideRightAndFade border border-light-35 dark:border-dark-35">
|
||||
<DropdownMenuItem @select="useRouter().push({ name: 'character-id-edit', params: { id: character.id } })" class="cursor-pointer text-base text-light-100 dark:text-dark-100 leading-none flex items-baseline py-1.5 relative ps-7 pe-4 select-none outline-none data-[disabled]:text-light-60 dark:data-[disabled]:text-dark-60 data-[disabled]:pointer-events-none data-[highlighted]:bg-light-35 dark:data-[highlighted]:bg-dark-35">
|
||||
<Icon icon="radix-icons:pencil-1" class="absolute left-1.5" />
|
||||
<span>Editer</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem @select="duplicateCharacter(character.id)" class="cursor-pointer text-base text-light-100 dark:text-dark-100 leading-none flex items-center py-1.5 relative ps-7 pe-4 select-none outline-none data-[disabled]:text-light-60 dark:data-[disabled]:text-dark-60 data-[disabled]:pointer-events-none data-[highlighted]:bg-light-35 dark:data-[highlighted]:bg-dark-35">
|
||||
<Icon icon="radix-icons:clipboard-copy" class="absolute left-1.5" />
|
||||
<span>Dupliquer</span>
|
||||
</DropdownMenuItem>
|
||||
<AlertDialogTrigger>
|
||||
<DropdownMenuItem class="cursor-pointer text-base text-light-red dark:text-dark-red leading-none flex items-center py-1.5 relative ps-7 pe-4 select-none outline-none data-[disabled]:text-light-60 dark:data-[disabled]:text-dark-60 data-[disabled]:pointer-events-none data-[highlighted]:bg-light-red dark:data-[highlighted]:bg-dark-red data-[highlighted]:bg-opacity-30 dark:data-[highlighted]:bg-opacity-30">
|
||||
<Icon icon="radix-icons:trash" class="absolute left-1.5" />
|
||||
<span>Supprimer</span>
|
||||
</DropdownMenuItem>
|
||||
</AlertDialogTrigger>
|
||||
|
||||
<DropdownMenuArrow class="fill-light-35 dark:fill-dark-35" />
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenuPortal>
|
||||
</DropdownMenuRoot>
|
||||
<AlertDialogPortal>
|
||||
<AlertDialogOverlay class="bg-light-0 dark:bg-dark-0 opacity-70 fixed inset-0 z-40" />
|
||||
<AlertDialogContent
|
||||
class="data-[state=open]:animate-contentShow fixed top-[50%] left-[50%] max-h-[85vh] w-[90vw] max-w-[800px] translate-x-[-50%] translate-y-[-50%] bg-light-10 dark:bg-dark-10 border border-light-30 dark:border-dark-30 p-6 z-50 text-light-100 dark:text-dark-100">
|
||||
<AlertDialogTitle class="text-3xl font-light relative -top-2">Supprimer {{ character.name }} ?</AlertDialogTitle>
|
||||
<div class="flex flex-1 justify-end gap-4">
|
||||
<AlertDialogCancel asChild><Button>Non</Button></AlertDialogCancel>
|
||||
<AlertDialogAction asChild><Button @click="() => deleteCharacter(character.id)" class="border-light-red dark:border-dark-red hover:border-light-red dark:hover:border-dark-red hover:bg-light-redBack dark:hover:bg-dark-redBack text-light-red dark:text-dark-red focus:shadow-light-red dark:focus:shadow-dark-red">Oui</Button></AlertDialogAction>
|
||||
</div>
|
||||
</AlertDialogContent>
|
||||
</AlertDialogPortal>
|
||||
</AlertDialogRoot>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<span>Erreur de chargement</span>
|
||||
<span>{{ error?.message }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
27
pages/character/list.client.vue
Normal file
27
pages/character/list.client.vue
Normal file
@@ -0,0 +1,27 @@
|
||||
<script setup lang="ts">
|
||||
const { data: characters, error, status } = await useFetch(`/api/character`, { params: { visibility: "public" } });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Head>
|
||||
<Title>d[any] - Liste des personnages</Title>
|
||||
</Head>
|
||||
<div class="flex flex-col">
|
||||
<div v-if="status === 'pending'" class="flex flex-1 justify-center align-center">
|
||||
<Loading size="large" />
|
||||
</div>
|
||||
<div v-else-if="status === 'success'" class="grid p-6 2xl:grid-cols-3 lg:grid-cols-2 grid-cols-1 gap-4 w-full">
|
||||
<div class="border border-light-30 dark:border-dark-30 p-3 flex flex-row gap-4" v-for="character of characters">
|
||||
<Avatar size="large" icon="radix-icons:person" src="" />
|
||||
<div class="flex flex-1 flex-shrink flex-col truncate">
|
||||
<NuxtLink class="text-xl font-bold hover:text-accent-blue truncate" :to="{ name: 'character-id', params: { id: character.id } }" :title="character.name">{{ character.name }}</NuxtLink>
|
||||
<span class="text-sm truncate">Niveau {{ character.progress.level }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<span>Erreur de chargement</span>
|
||||
<span>{{ error?.message }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,27 +1,93 @@
|
||||
import { and, eq, sql } from 'drizzle-orm';
|
||||
import { and, eq, SQL, sql, type Operators } from 'drizzle-orm';
|
||||
import useDatabase from '~/composables/useDatabase';
|
||||
import { characterTable } from '~/db/schema';
|
||||
import type { Character } from '~/types/character';
|
||||
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';
|
||||
|
||||
export default defineEventHandler(async (e) => {
|
||||
const session = await getUserSession(e);
|
||||
let { visibility } = getQuery(e) as { visibility?: "public" | "own" | "admin" };
|
||||
|
||||
if(!session.user)
|
||||
if(!visibility)
|
||||
{
|
||||
setResponseStatus(e, 401);
|
||||
return;
|
||||
visibility = "own";
|
||||
}
|
||||
|
||||
let where: ((character: typeof characterTable._.config.columns, sql: Operators) => SQL | undefined) | undefined = undefined;
|
||||
const db = useDatabase();
|
||||
const character = db.select({
|
||||
id: characterTable.id,
|
||||
name: characterTable.name,
|
||||
progress: characterTable.progress,
|
||||
}).from(characterTable).where(eq(characterTable.owner, session.user.id)).all();
|
||||
|
||||
if(character !== undefined)
|
||||
if(visibility === "own")
|
||||
{
|
||||
return character as Character[];
|
||||
const session = await getUserSession(e);
|
||||
if(!session.user)
|
||||
{
|
||||
setResponseStatus(e, 401);
|
||||
return;
|
||||
}
|
||||
|
||||
where = (character, { eq, and }) => and(eq(character.owner, session.user!.id), eq(character.visibility, "private"));
|
||||
}
|
||||
else if(visibility === 'public')
|
||||
{
|
||||
where = (character, { eq, and }) => eq(character.visibility, "public");
|
||||
}
|
||||
else if(visibility === 'admin')
|
||||
{
|
||||
const session = await getUserSession(e);
|
||||
if(!session.user)
|
||||
{
|
||||
setResponseStatus(e, 401);
|
||||
return;
|
||||
}
|
||||
const db = useDatabase();
|
||||
|
||||
const rights = db.select({ right: userPermissionsTable.permission }).from(userPermissionsTable).where(eq(userPermissionsTable.id, session.user.id)).all();
|
||||
if(rights.length === 0 || !hasPermissions(rights.map(e => e.right), ['admin']))
|
||||
{
|
||||
setResponseStatus(e, 403);
|
||||
return;
|
||||
}
|
||||
|
||||
where = undefined;
|
||||
}
|
||||
|
||||
const characters = db.query.characterTable.findMany({
|
||||
with: {
|
||||
abilities: true,
|
||||
levels: true,
|
||||
modifiers: true,
|
||||
spells: true,
|
||||
training: true,
|
||||
user: {
|
||||
columns: { username: true }
|
||||
}
|
||||
},
|
||||
where: where,
|
||||
}).sync();
|
||||
|
||||
if(characters !== undefined)
|
||||
{
|
||||
return characters.map(character => ({
|
||||
id: character.id,
|
||||
|
||||
name: character.name,
|
||||
people: character.people,
|
||||
level: character.level,
|
||||
aspect: character.aspect,
|
||||
notes: character.notes,
|
||||
health: character.health,
|
||||
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>[]>),
|
||||
leveling: character.levels.map(e => [e.level as Level, e.choice] as DoubleIndex<Level>),
|
||||
abilities: group(character.abilities.map(e => ({ ...e, value: [e.value, e.max] as [number, number] })), "ability", "value"),
|
||||
spells: character.spells.map(e => e.value),
|
||||
modifiers: group(character.modifiers, "modifier", "value"),
|
||||
|
||||
owner: character.owner,
|
||||
username: character.user.username,
|
||||
visibility: character.visibility,
|
||||
} as Character));
|
||||
}
|
||||
|
||||
setResponseStatus(e, 404);
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import { z } from 'zod';
|
||||
import useDatabase from '~/composables/useDatabase';
|
||||
import { characterTable } from '~/db/schema';
|
||||
import { characterAbilitiesTable, characterLevelingTable, characterModifiersTable, characterSpellsTable, characterTable, characterTrainingTable } from '~/db/schema';
|
||||
import { CharacterValidation, type Ability, type DoubleIndex, type MainStat, type TrainingLevel } from '~/types/character';
|
||||
|
||||
|
||||
export default defineEventHandler(async (e) => {
|
||||
const body = await readBody(e);
|
||||
if(!body)
|
||||
const body = await readValidatedBody(e, CharacterValidation.extend({ id: z.unknown(), }).safeParse);
|
||||
if(!body.success)
|
||||
{
|
||||
setResponseStatus(e, 400);
|
||||
return;
|
||||
return body.error.message;
|
||||
}
|
||||
|
||||
const session = await getUserSession(e);
|
||||
@@ -18,12 +21,46 @@ export default defineEventHandler(async (e) => {
|
||||
|
||||
const db = useDatabase();
|
||||
|
||||
const id = await db.insert(characterTable).values({
|
||||
name: body.name,
|
||||
progress: body.progress,
|
||||
owner: session.user.id,
|
||||
}).returning({ id: characterTable.id });
|
||||
try
|
||||
{
|
||||
const id = db.transaction((tx) => {
|
||||
const id = tx.insert(characterTable).values({
|
||||
name: body.data.name,
|
||||
owner: session.user!.id,
|
||||
people: body.data.people!,
|
||||
level: body.data.level,
|
||||
aspect: body.data.aspect,
|
||||
notes: body.data.notes,
|
||||
health: body.data.health,
|
||||
mana: body.data.mana,
|
||||
visibility: body.data.visibility,
|
||||
thumbnail: body.data.thumbnail,
|
||||
}).returning({ id: characterTable.id }).get().id;
|
||||
|
||||
if(body.data.leveling.length > 0) tx.insert(characterLevelingTable).values(body.data.leveling.map(e => ({ character: id, level: e[0], choice: e[1] }))).run();
|
||||
|
||||
setResponseStatus(e, 201);
|
||||
return id[0].id;
|
||||
const training = Object.entries(body.data.training).flatMap(e => e[1].map(_e => ({ character: id, stat: e[0] as MainStat, level: _e[0], choice: _e[1] })));
|
||||
if(training.length > 0) tx.insert(characterTrainingTable).values(training).run();
|
||||
|
||||
const modifiers = Object.entries(body.data.modifiers).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).map(e => ({ character: id, ability: e[0] as Ability, value: e[1][0], max: e[1][1] }));
|
||||
if(abilities.length > 0) tx.insert(characterAbilitiesTable).values(abilities).run();
|
||||
|
||||
return id;
|
||||
});
|
||||
|
||||
setResponseStatus(e, 201);
|
||||
return id;
|
||||
}
|
||||
catch(_e)
|
||||
{
|
||||
console.error(_e);
|
||||
|
||||
setResponseStatus(e, 500);
|
||||
return;
|
||||
}
|
||||
});
|
||||
@@ -1,7 +1,8 @@
|
||||
import { and, eq, sql } from 'drizzle-orm';
|
||||
import useDatabase from '~/composables/useDatabase';
|
||||
import { characterTable } from '~/db/schema';
|
||||
import type { Character } from '~/types/character';
|
||||
import { group } from '~/shared/general.util';
|
||||
import type { Character, DoubleIndex, Level, MainStat, TrainingLevel } from '~/types/character';
|
||||
|
||||
export default defineEventHandler(async (e) => {
|
||||
const id = getRouterParam(e, "id");
|
||||
@@ -21,16 +22,43 @@ export default defineEventHandler(async (e) => {
|
||||
}
|
||||
|
||||
const db = useDatabase();
|
||||
const character = db.select({
|
||||
id: characterTable.id,
|
||||
name: characterTable.name,
|
||||
progress: characterTable.progress,
|
||||
owner: characterTable.owner
|
||||
}).from(characterTable).where(and(eq(characterTable.id, id), eq(characterTable.owner, session.user.id))).get();
|
||||
const character = db.query.characterTable.findFirst({
|
||||
with: {
|
||||
abilities: true,
|
||||
levels: true,
|
||||
modifiers: true,
|
||||
spells: true,
|
||||
training: true,
|
||||
user: {
|
||||
columns: { username: true }
|
||||
}
|
||||
},
|
||||
where: (character, { eq, and }) => and(eq(character.id, parseInt(id, 10)), eq(characterTable.owner, session.user!.id)),
|
||||
}).sync();
|
||||
|
||||
if(character !== undefined)
|
||||
{
|
||||
return character as Character;
|
||||
return {
|
||||
id: character.id,
|
||||
|
||||
name: character.name,
|
||||
people: character.people,
|
||||
level: character.level,
|
||||
aspect: character.aspect,
|
||||
notes: character.notes,
|
||||
health: character.health,
|
||||
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>[]>),
|
||||
leveling: character.levels.map(e => [e.level as Level, e.choice] as DoubleIndex<Level>),
|
||||
abilities: group(character.abilities.map(e => ({ ...e, value: [e.value, e.max] as [number, number] })), "ability", "value"),
|
||||
spells: character.spells.map(e => e.value),
|
||||
modifiers: group(character.modifiers, "modifier", "value"),
|
||||
|
||||
owner: character.owner,
|
||||
username: character.user.username,
|
||||
visibility: character.visibility,
|
||||
} as Character;
|
||||
}
|
||||
|
||||
setResponseStatus(e, 404);
|
||||
|
||||
@@ -1,24 +1,26 @@
|
||||
import { eq } from 'drizzle-orm';
|
||||
import useDatabase from '~/composables/useDatabase';
|
||||
import { characterTable } from '~/db/schema';
|
||||
import { characterAbilitiesTable, characterLevelingTable, characterModifiersTable, characterSpellsTable, characterTable, characterTrainingTable } from '~/db/schema';
|
||||
import { CharacterValidation, type Ability, type MainStat } from '~/types/character';
|
||||
|
||||
export default defineEventHandler(async (e) => {
|
||||
const id = getRouterParam(e, "id");
|
||||
if(!id)
|
||||
const params = getRouterParam(e, "id");
|
||||
if(!params)
|
||||
{
|
||||
setResponseStatus(e, 400);
|
||||
return;
|
||||
}
|
||||
const id = parseInt(params, 10);
|
||||
|
||||
const body = await readBody(e);
|
||||
if(!body)
|
||||
const body = await readValidatedBody(e, CharacterValidation.safeParse);
|
||||
if(!body.success)
|
||||
{
|
||||
setResponseStatus(e, 400);
|
||||
return;
|
||||
return body.error.message;
|
||||
}
|
||||
|
||||
const db = useDatabase();
|
||||
const old = db.select({ id: characterTable.id, owner: characterTable.owner }).from(characterTable).where(eq(characterTable.id, parseInt(id))).get();
|
||||
const old = db.select({ id: characterTable.id, owner: characterTable.owner }).from(characterTable).where(eq(characterTable.id, id)).get();
|
||||
|
||||
if(!old)
|
||||
{
|
||||
@@ -32,11 +34,39 @@ export default defineEventHandler(async (e) => {
|
||||
setResponseStatus(e, 401);
|
||||
return;
|
||||
}
|
||||
|
||||
db.transaction((tx) => {
|
||||
tx.update(characterTable).set({
|
||||
name: body.data.name,
|
||||
people: body.data.people!,
|
||||
level: body.data.level,
|
||||
aspect: body.data.aspect,
|
||||
notes: body.data.notes,
|
||||
health: body.data.health,
|
||||
mana: body.data.mana,
|
||||
visibility: body.data.visibility,
|
||||
thumbnail: body.data.thumbnail,
|
||||
}).where(eq(characterTable.id, id)).run();
|
||||
|
||||
db.update(characterTable).set({
|
||||
name: body.name,
|
||||
progress: body.progress,
|
||||
}).where(eq(characterTable.id, parseInt(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();
|
||||
|
||||
if(body.data.leveling.length > 0) tx.insert(characterLevelingTable).values(body.data.leveling.map(e => ({ character: id, level: e[0], choice: e[1] }))).run();
|
||||
|
||||
const training = Object.entries(body.data.training).flatMap(e => e[1].map(_e => ({ character: id, stat: e[0] as MainStat, level: _e[0], choice: _e[1] })));
|
||||
if(training.length > 0) tx.insert(characterTrainingTable).values(training).run();
|
||||
|
||||
const modifiers = Object.entries(body.data.modifiers).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).map(e => ({ character: id, ability: e[0] as Ability, value: e[1][0], max: e[1][1] }));
|
||||
if(abilities.length > 0) tx.insert(characterAbilitiesTable).values(abilities).run();
|
||||
});
|
||||
|
||||
await useStorage('cache').removeItem(`nitro:functions:character:${id}.json`);
|
||||
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { and, eq } from 'drizzle-orm';
|
||||
import useDatabase from '~/composables/useDatabase';
|
||||
import { characterTable } from '~/db/schema';
|
||||
import type { Ability, Character, CharacterConfig, CompiledCharacter, DoubleIndex, Feature, MainStat, TrainingLevel, TrainingOption } from '~/types/character';
|
||||
import { defaultCharacter, type Ability, type Character, type CharacterConfig, type CompiledCharacter, type DoubleIndex, type Feature, type Level, type MainStat, type TrainingLevel, type TrainingOption } from '~/types/character';
|
||||
import characterData from '#shared/character-config.json';
|
||||
import { users } from '~/drizzle/schema';
|
||||
import { group } from '~/shared/general.util';
|
||||
|
||||
export default defineEventHandler(async (e) => {
|
||||
const id = getRouterParam(e, "id");
|
||||
@@ -14,109 +12,55 @@ export default defineEventHandler(async (e) => {
|
||||
}
|
||||
|
||||
const db = useDatabase();
|
||||
const character = db.select({
|
||||
id: characterTable.id,
|
||||
name: characterTable.name,
|
||||
progress: characterTable.progress,
|
||||
owner: characterTable.owner,
|
||||
username: users.username
|
||||
}).from(characterTable).leftJoin(users, eq(characterTable.owner, users.id)).where(and(eq(characterTable.id, parseInt(id)))).get();
|
||||
const character = db.query.characterTable.findFirst({
|
||||
with: {
|
||||
abilities: true,
|
||||
levels: true,
|
||||
modifiers: true,
|
||||
spells: true,
|
||||
training: true,
|
||||
user: {
|
||||
columns: { username: true }
|
||||
}
|
||||
},
|
||||
where: (character, { eq }) => eq(character.id, parseInt(id, 10)),
|
||||
}).sync();
|
||||
|
||||
if(character !== undefined)
|
||||
{
|
||||
return compileCharacter(character as Character & { username: string });
|
||||
return compileCharacter(Object.assign(defaultCharacter, {
|
||||
id: character.id,
|
||||
|
||||
name: character.name,
|
||||
people: character.people,
|
||||
level: character.level,
|
||||
aspect: character.aspect,
|
||||
notes: character.notes,
|
||||
health: character.health,
|
||||
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>[]>),
|
||||
leveling: character.levels.map(e => [e.level as Level, e.choice] as DoubleIndex<Level>),
|
||||
abilities: group(character.abilities.map(e => ({ ...e, value: [e.value, e.max] as [number, number] })), "ability", "value"),
|
||||
spells: character.spells.map(e => e.value),
|
||||
modifiers: group(character.modifiers, "modifier", "value"),
|
||||
|
||||
owner: character.owner,
|
||||
username: character.user.username,
|
||||
visibility: character.visibility,
|
||||
} as Character) as Character);
|
||||
}
|
||||
|
||||
setResponseStatus(e, 404);
|
||||
return;
|
||||
}/* , { name: "character", getKey: (e) => getRouterParam(e, "id") || 'error' } */);
|
||||
|
||||
/*
|
||||
Athlétisme
|
||||
La capacité à effectuer un acte physique intense ou prolongé. Permet de pousser, contraindre, nager, courir.
|
||||
|
||||
Force + Constitution.
|
||||
|
||||
Acrobatique
|
||||
La capacité à se mouvoir avec souplesse sous la contrainte. Permet d'escalader, d'enjamber, de sauter.
|
||||
|
||||
Force + Dextérité.
|
||||
|
||||
Intimidation
|
||||
La capacité à intimider et inspirer la crainte.
|
||||
|
||||
Force + Charisme.
|
||||
|
||||
Doigté
|
||||
La capacité à faire des actions précises avec ses mains. Permet de voler à la tire, de crocheter.
|
||||
|
||||
Dextérité + Dextérité.
|
||||
|
||||
Discrétion
|
||||
La capacité à dissimuler sa présence. Permet de se cacher, de se mouvoir sans bruit.
|
||||
|
||||
Dextérité + Dextérité.
|
||||
|
||||
Survie
|
||||
La capacité à survivre dans des conditions difficiles. Permet de pister, de collecter de la nourriture, de retrouver son chemin.
|
||||
|
||||
Constitution + Psyché.
|
||||
|
||||
Enquête
|
||||
La capacité à demander au MJ de l'aide parce que vous puez la merde.
|
||||
|
||||
Intelligence + Curiosité.
|
||||
|
||||
Histoire
|
||||
La capacité à connaitre le passé du monde.
|
||||
|
||||
Intelligence + Curiosité.
|
||||
|
||||
Religion
|
||||
La capacité a connaitre les pratiques et les coutumes religieuses.
|
||||
|
||||
Intelligence + Curiosité.
|
||||
|
||||
Arcanes
|
||||
La capacité à comprendre et percevoir la magie. Permet de comprendre un sort en cours, de détecter de la magie.
|
||||
|
||||
Intelligence + Psyché.
|
||||
|
||||
Compréhension
|
||||
La capacité à déterminer les intentions des interlocuteurs. Permet de déceler des mensonges, de l'influence.
|
||||
|
||||
Intelligence + Charisme.
|
||||
|
||||
Perception
|
||||
La capacité à observer le monde à travers ces sens. Permet d'observer, d'entendre, de sentir.
|
||||
|
||||
Curiosité + Curiosité.
|
||||
|
||||
Représentation
|
||||
La capacité à se mettre en scène et à utiliser les arts. Permet de se produire en spectacle, de jouer d'un instrument, de chanter, de danser.
|
||||
|
||||
Curiosité + Charisme.
|
||||
|
||||
Médicine
|
||||
La capacité à apporter des soins. Permet de stabiliser un joueur mourant, de soigner durant un repos.
|
||||
|
||||
Curiosité + Psyché.
|
||||
|
||||
Persuasion
|
||||
Charisme + Psyché.
|
||||
|
||||
Dressage
|
||||
Charisme + Psyché.
|
||||
|
||||
Mensonge
|
||||
Charisme + Psyché.
|
||||
*/
|
||||
function compileCharacter(character: Character & { username?: string }): CompiledCharacter
|
||||
{
|
||||
const config = characterData as CharacterConfig;
|
||||
const race = character.progress.race.index !== undefined ? config.peoples[character.progress.race.index] : undefined;
|
||||
const raceOptions = race ? character.progress.race.progress!.map(e => race.options[e[0]][e[1]]) : [];
|
||||
const features = Object.entries(config.training).map(e => [e[0], getFeaturesOf(e[0] as MainStat, character.progress.training[e[0] as MainStat])]) as [MainStat, TrainingOption[]][];
|
||||
const race = character.people !== undefined ? config.peoples[character.people] : undefined;
|
||||
const raceOptions = race ? character.leveling!.map(e => race.options[e[0]][e[1]]) : [];
|
||||
const features = Object.entries(config.training).map(e => [e[0], getFeaturesOf(e[0] as MainStat, character.training[e[0] as MainStat])]) as [MainStat, TrainingOption[]][];
|
||||
|
||||
const compiled: CompiledCharacter = {
|
||||
id: character.id,
|
||||
@@ -125,9 +69,13 @@ function compileCharacter(character: Character & { username?: string }): Compile
|
||||
name: character.name,
|
||||
health: raceOptions.reduce((p, v) => p + (v.health ?? 0), 0),
|
||||
mana: raceOptions.reduce((p, v) => p + (v.mana ?? 0), 0),
|
||||
race: character.progress.race.index ?? -1,
|
||||
modifier: features.map(e => [e[0], Math.floor((e[1].length - 1) / 3) + (character.progress.modifiers[e[0]] ?? 0)] as [MainStat, number]).reduce((p, v) => { p[v[0]] = v[1]; return p }, {} as Record<MainStat, number>),
|
||||
level: character.progress.level,
|
||||
race: character.people!,
|
||||
modifier: features.map(e => [e[0], Math.floor((e[1].length - 1) / 3) + (character.modifiers[e[0]] ?? 0)] as [MainStat, number]).reduce((p, v) => { p[v[0]] = v[1]; return p }, {} as Record<MainStat, number>),
|
||||
level: character.level,
|
||||
values: {
|
||||
health: character.health,
|
||||
mana: character.mana
|
||||
},
|
||||
features: {
|
||||
action: [],
|
||||
reaction: [],
|
||||
@@ -161,6 +109,7 @@ function compileCharacter(character: Character & { username?: string }): Compile
|
||||
precision: 0,
|
||||
arts: 0,
|
||||
},
|
||||
spells: character.spells ?? [],
|
||||
speed: false,
|
||||
defense: {
|
||||
static: 6,
|
||||
@@ -193,13 +142,13 @@ function compileCharacter(character: Character & { username?: string }): Compile
|
||||
},
|
||||
initiative: 0,
|
||||
aspect: "",
|
||||
notes: character.progress.notes,
|
||||
notes: character.notes ?? "",
|
||||
};
|
||||
|
||||
features.forEach(e => e[1].forEach((_e, i) => applyTrainingOption(e[0], _e, compiled, i === e[1].length - 1)));
|
||||
specialFeatures(compiled, character.progress.training);
|
||||
specialFeatures(compiled, character.training);
|
||||
|
||||
Object.entries(character.progress.abilities).forEach(e => compiled.abilities[e[0] as Ability]! += e[1][0]);
|
||||
Object.entries(character.abilities).forEach(e => compiled.abilities[e[0] as Ability]! += e[1][0]);
|
||||
|
||||
return compiled;
|
||||
}
|
||||
@@ -215,6 +164,7 @@ function applyTrainingOption(stat: MainStat, option: TrainingOption, character:
|
||||
if(option.resistance) option.resistance.forEach(e => character.resistance[e[0]][e[1] === "attack" ? 0 : 1]++);
|
||||
if(option.spellslot) character.spellslots += option.spellslot in character.modifier ? character.modifier[option.spellslot as MainStat] : option.spellslot as number;
|
||||
if(option.arts) character.artslots += option.arts in character.modifier ? character.modifier[option.arts as MainStat] : option.arts as number;
|
||||
if(option.spell) character.spells.push(option.spell);
|
||||
|
||||
option.description.forEach(line => !line.disposable && (last || !line.replaced) && character.features[line.category ?? "misc"].push(line.text));
|
||||
|
||||
|
||||
37
server/api/character/[id]/duplicate.post.ts
Normal file
37
server/api/character/[id]/duplicate.post.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { eq } from 'drizzle-orm';
|
||||
import useDatabase from '~/composables/useDatabase';
|
||||
import { characterTable } from '~/db/schema';
|
||||
|
||||
export default defineEventHandler(async (e) => {
|
||||
const id = getRouterParam(e, "id");
|
||||
if(!id)
|
||||
{
|
||||
setResponseStatus(e, 400);
|
||||
return;
|
||||
}
|
||||
|
||||
const db = useDatabase();
|
||||
const old = db.select().from(characterTable).where(eq(characterTable.id, parseInt(id, 10))).get();
|
||||
|
||||
if(!old)
|
||||
{
|
||||
setResponseStatus(e, 404);
|
||||
return;
|
||||
}
|
||||
|
||||
const session = await getUserSession(e);
|
||||
if(!session.user || old.owner !== session.user.id || session.user.state !== 1)
|
||||
{
|
||||
setResponseStatus(e, 401);
|
||||
return;
|
||||
}
|
||||
|
||||
const returned = await db.insert(characterTable).values({
|
||||
name: `Copie de ${old.name}`,
|
||||
progress: old.progress,
|
||||
owner: session.user.id,
|
||||
}).returning({ id: characterTable.id });
|
||||
|
||||
setResponseStatus(e, 201);
|
||||
return returned[0].id;
|
||||
});
|
||||
34
server/api/character/[id]/values.get.ts
Normal file
34
server/api/character/[id]/values.get.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { and, eq, sql } from 'drizzle-orm';
|
||||
import useDatabase from '~/composables/useDatabase';
|
||||
import { characterTable } from '~/db/schema';
|
||||
import type { Character, CharacterValues } from '~/types/character';
|
||||
|
||||
export default defineEventHandler(async (e) => {
|
||||
const id = getRouterParam(e, "id");
|
||||
|
||||
if(!id)
|
||||
{
|
||||
setResponseStatus(e, 400);
|
||||
return;
|
||||
}
|
||||
|
||||
const session = await getUserSession(e);
|
||||
if(!session.user)
|
||||
{
|
||||
setResponseStatus(e, 401);
|
||||
return;
|
||||
}
|
||||
|
||||
const db = useDatabase();
|
||||
const character = db.select({
|
||||
values: characterTable.values
|
||||
}).from(characterTable).where(and(eq(characterTable.id, parseInt(id, 10)), eq(characterTable.owner, session.user.id))).get();
|
||||
|
||||
if(character !== undefined)
|
||||
{
|
||||
return character.values as CharacterValues;
|
||||
}
|
||||
|
||||
setResponseStatus(e, 404);
|
||||
return;
|
||||
});
|
||||
42
server/api/character/[id]/values.post.ts
Normal file
42
server/api/character/[id]/values.post.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { eq } from 'drizzle-orm';
|
||||
import useDatabase from '~/composables/useDatabase';
|
||||
import { characterTable } from '~/db/schema';
|
||||
|
||||
export default defineEventHandler(async (e) => {
|
||||
const id = getRouterParam(e, "id");
|
||||
if(!id)
|
||||
{
|
||||
setResponseStatus(e, 400);
|
||||
return;
|
||||
}
|
||||
|
||||
const body = await readBody(e);
|
||||
if(!body)
|
||||
{
|
||||
setResponseStatus(e, 400);
|
||||
return;
|
||||
}
|
||||
|
||||
const db = useDatabase();
|
||||
const old = db.select({ id: characterTable.id, owner: characterTable.owner }).from(characterTable).where(eq(characterTable.id, parseInt(id, 10))).get();
|
||||
|
||||
if(!old)
|
||||
{
|
||||
setResponseStatus(e, 404);
|
||||
return;
|
||||
}
|
||||
|
||||
const session = await getUserSession(e);
|
||||
if(!session.user || old.owner !== session.user.id || session.user.state !== 1)
|
||||
{
|
||||
setResponseStatus(e, 401);
|
||||
return;
|
||||
}
|
||||
|
||||
db.update(characterTable).set({
|
||||
values: body,
|
||||
}).where(eq(characterTable.id, parseInt(id, 10))).run();
|
||||
|
||||
setResponseStatus(e, 200);
|
||||
return;
|
||||
});
|
||||
@@ -39,24 +39,24 @@
|
||||
},
|
||||
"options": {
|
||||
"1": [ { "training": 35, "health": 14 } ],
|
||||
"2": [ { "training": 1, "health": 4, "mana": 2 }, { "health": 7, "mana": 4, "abilities": 1 } ],
|
||||
"3": [ { "training": 2, "health": 4, "mana": 2, "abilities": 1 } ],
|
||||
"2": [ { "training": 1, "health": 3, "mana": 2 }, { "health": 6, "mana": 3, "abilities": 1 } ],
|
||||
"3": [ { "training": 2, "health": 3, "mana": 1, "abilities": 1 } ],
|
||||
"4": [ { "training": 1, "health": 4, "mana": 2, "abilities": 2 } ],
|
||||
"5": [ { "training": 1, "health": 6, "mana": 2, "abilities": 2 }, { "training": 1, "shaping": 1, "health": 9, "mana": 5 }, { "training": 2, "health": 8, "mana": 3 } ],
|
||||
"6": [ { "training": 1, "health": 3, "mana": 3 }, { "training": 1, "abilities": 3 } ],
|
||||
"7": [ { "training": 2, "health": 4, "mana": 6 }, { "training": 2, "health": 6, "mana": 2 } ],
|
||||
"8": [ { "training": 3 }, { "training": 1, "health": 8, "mana": 8 } ],
|
||||
"9": [ { "training": 1, "health": 4, "mana": 6 }, { "training": 1, "health": 3, "mana": 1, "abilities": 2 } ],
|
||||
"5": [ { "training": 1, "health": 4, "mana": 2, "abilities": 2 }, { "training": 1, "shaping": 1, "health": 8, "mana": 4 }, { "training": 2, "health": 7, "mana": 2 } ],
|
||||
"6": [ { "training": 1, "health": 3, "mana": 3 }, { "training": 1, "abilities": 3, "spellslots": 1 } ],
|
||||
"7": [ { "training": 2, "health": 3, "mana": 5 }, { "training": 2, "health": 5, "mana": 2 } ],
|
||||
"8": [ { "training": 3 }, { "training": 1, "health": 6, "mana": 6, "spellslots": 1 } ],
|
||||
"9": [ { "training": 1, "health": 3, "mana": 5 }, { "training": 1, "health": 2, "abilities": 2 } ],
|
||||
"10": [ { "training": 2 }, { "training": 1, "shaping": 1, "abilities": 2 }, { "modifier": 1, "abilities": 1 } ],
|
||||
"11": [ { "training": 1, "health": 8, "mana": 1 }, { "training": 1, "health": 3, "mana": 5 }, { "training": 1, "abilities": 2 } ],
|
||||
"12": [ { "training": 2, "health": 4, "mana": 2 }, { "training": 2, "health": 8 }, { "training": 2, "mana": 7 } ],
|
||||
"11": [ { "training": 1, "health": 7, "mana": 1 }, { "training": 1, "health": 2, "mana": 5 }, { "training": 1, "abilities": 2 } ],
|
||||
"12": [ { "training": 2, "spellslots": 1 }, { "training": 2, "health": 8 }, { "training": 2, "mana": 7 } ],
|
||||
"13": [ { "training": 1, "health": 2, "mana": 2, "abilities": 1 }, { "training": 1, "shaping": 1, "health": 4, "mana": 4 } ],
|
||||
"14": [ { "training": 3, "health": 4, "mana": 4 }, { "training": 3, "health": 6, "mana": 2 } ],
|
||||
"15": [ { "training": 1 }, { "health": 6, "mana": 6, "abilities": 1 } ],
|
||||
"16": [ { "training": 1, "health": 4, "mana": 6 }, { "training": 1, "health": 6, "mana": 2 } ],
|
||||
"17": [ { "training": 2, "abilities": 1 }, { "training": 1, "shaping": 1, "abilities": 2 }, { "training": 1, "health": 6, "mana": 4, "abilities": 1 } ],
|
||||
"14": [ { "training": 3, "health": 3, "mana": 5 }, { "training": 3, "health": 6, "mana": 1 } ],
|
||||
"15": [ { "training": 1 }, { "health": 5, "mana": 5, "abilities": 1 } ],
|
||||
"16": [ { "training": 1, "health": 3, "mana": 5 }, { "training": 1, "health": 5, "mana": 2 } ],
|
||||
"17": [ { "training": 2, "abilities": 1, "spellslots": 1 }, { "training": 1, "shaping": 1, "abilities": 2, "spellslots": 1 }, { "training": 1, "health": 7, "mana": 5, "abilities": 1 } ],
|
||||
"18": [ { "training": 1, "health": 6, "mana": 1 }, { "training": 1, "health": 2, "mana": 5 } ],
|
||||
"19": [ { "training": 2, "health": 6, "mana": 2, "abilities": 1 }, { "training": 2, "health": 3, "mana": 5, "abilities": 1 } ],
|
||||
"19": [ { "training": 2, "health": 6, "mana": 3, "abilities": 2 }, { "training": 2, "health": 2, "mana": 5, "spellslots": 1 } ],
|
||||
"20": [ { "training": 2 }, { "modifier": 1, "abilities": 1 } ]
|
||||
}
|
||||
}
|
||||
@@ -1555,10 +1555,11 @@
|
||||
{
|
||||
"description": [
|
||||
{
|
||||
"text": "",
|
||||
"disposable": false
|
||||
"text": "Vous avez un bonus de +1 aux jets de résistance des [[1. Magie#Les sorts de savoir|sorts de savoir]] en tant qu'attaquant.",
|
||||
"disposable": true
|
||||
}
|
||||
]
|
||||
],
|
||||
"resistance": [["knowledge", "attack"]]
|
||||
}
|
||||
],
|
||||
"11": [
|
||||
@@ -2079,9 +2080,10 @@
|
||||
"description": [
|
||||
{
|
||||
"text": "Vous augmentez le modifieur de votre choix de 1.",
|
||||
"disposable": false
|
||||
"disposable": true
|
||||
}
|
||||
]
|
||||
],
|
||||
"modifier": 1
|
||||
},
|
||||
{
|
||||
"description": [
|
||||
@@ -2858,5 +2860,742 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"spells": [
|
||||
{
|
||||
"name": "Trait de feu",
|
||||
"rank": 1,
|
||||
"type": "precision",
|
||||
"cost": 3,
|
||||
"speed": "action",
|
||||
"elements": [
|
||||
"fire"
|
||||
],
|
||||
"effect": "Faites un jet d'attaque avec la [[1. Entrainement#La dextérité|dextérité]]. Tire un faisceau de flamme, infligeant 2d8 dégâts de feu en touchant.",
|
||||
"tags": [
|
||||
"Dégats"
|
||||
],
|
||||
"id": "0"
|
||||
},
|
||||
{
|
||||
"name": "Echauffement",
|
||||
"rank": 1,
|
||||
"type": "knowledge",
|
||||
"cost": 2,
|
||||
"speed": "action",
|
||||
"elements": [
|
||||
"fire"
|
||||
],
|
||||
"effect": "Chauffe à blanc une arme ou un projectile. Jusqu'au début de votre prochain tour, les coups portés avec l'objet infligent 1d6 dégâts supplémentaire. Les dégâts de l'arme deviennent des dégâts de feu.",
|
||||
"tags": [
|
||||
"Buff"
|
||||
],
|
||||
"id": "1"
|
||||
},
|
||||
{
|
||||
"name": "Projection bouillonnante",
|
||||
"rank": 1,
|
||||
"type": "precision",
|
||||
"cost": 6,
|
||||
"speed": "action",
|
||||
"elements": [
|
||||
"fire"
|
||||
],
|
||||
"effect": "Lance un projectile de feu éclatant sur 3 cases de rayon. Chaque personne dans le rayon doit réussir un [[3. Résistance aux chocs#Le jet de résistance|jet de résistance]](d10/6 + mod. d'[[1. Entrainement#L'intelligence|intelligence]]) ou subit 2d8 dégâts de feu.",
|
||||
"tags": [
|
||||
"Dégats"
|
||||
],
|
||||
"id": "2"
|
||||
},
|
||||
{
|
||||
"name": "Corps ardent",
|
||||
"rank": 1,
|
||||
"type": "knowledge",
|
||||
"cost": 6,
|
||||
"speed": "action",
|
||||
"elements": [
|
||||
"fire"
|
||||
],
|
||||
"effect": "Pendant 5 tours, toute personne terminant son tour à une case de vous subit 1d10 dégâts de feu.",
|
||||
"tags": [
|
||||
"Dégats"
|
||||
],
|
||||
"id": "3"
|
||||
},
|
||||
{
|
||||
"name": "Gravure marquante",
|
||||
"rank": 1,
|
||||
"type": "knowledge",
|
||||
"cost": 3,
|
||||
"speed": 10,
|
||||
"elements": [
|
||||
"fire"
|
||||
],
|
||||
"effect": "Grave une marque discrète sur un objet, restant durant 3 jours ou jusqu'à ce que quelqu'un rentre en contact avec la marque, auquel cas cette dernière lui sera gravée avec une désagréable sensation de brulure. La brulure disparait après 3 jours.",
|
||||
"tags": [
|
||||
"Utilitaire"
|
||||
],
|
||||
"id": "4"
|
||||
},
|
||||
{
|
||||
"name": "Protection supérieure",
|
||||
"rank": 1,
|
||||
"type": "instinct",
|
||||
"cost": 3,
|
||||
"speed": "reaction",
|
||||
"elements": [
|
||||
"ice"
|
||||
],
|
||||
"effect": "Votre armure subit l'intégralité des dégâts sur le prochain coup.",
|
||||
"tags": [
|
||||
"Tank"
|
||||
],
|
||||
"id": "5"
|
||||
},
|
||||
{
|
||||
"name": "Lames de glace",
|
||||
"rank": 1,
|
||||
"type": "precision",
|
||||
"cost": 3,
|
||||
"speed": "action",
|
||||
"elements": [
|
||||
"ice"
|
||||
],
|
||||
"effect": "Faites un jet d'attaque avec la [[1. Entrainement#La dextérité|dextérité]] en touchant. Tire 2 projectiles infligeant 1d8 dégâts de glace. *Augmenter les dés de dégâts offre un projectile supplémentaire à la place. Chaque projectile demande un jet d'attaque séparé et peut viser une cible différente.*",
|
||||
"tags": [
|
||||
"Dégats"
|
||||
],
|
||||
"id": "6"
|
||||
},
|
||||
{
|
||||
"name": "Chaine de foudre",
|
||||
"rank": 1,
|
||||
"type": "precision",
|
||||
"cost": 3,
|
||||
"speed": "action",
|
||||
"elements": [
|
||||
"thunder"
|
||||
],
|
||||
"effect": "Faites un jet d'attaque avec la [[1. Entrainement#Dextérité|dextérité]]. Frappe une cible visible puis rebondit sur jusqu'à 2 autres cibles. Inflige 1d8[[2. Glossaire#Jet explosif|!]] dégâts de foudre.",
|
||||
"tags": [
|
||||
"Dégats"
|
||||
],
|
||||
"id": "7"
|
||||
},
|
||||
{
|
||||
"name": "Vitesse lumière",
|
||||
"rank": 1,
|
||||
"type": "knowledge",
|
||||
"cost": 2,
|
||||
"speed": "action",
|
||||
"elements": [
|
||||
"thunder"
|
||||
],
|
||||
"effect": "Se téléporte à 6 cases tant que vous pouvez voir et courir vers la destination.",
|
||||
"tags": [
|
||||
"Mouvement"
|
||||
],
|
||||
"id": "8"
|
||||
},
|
||||
{
|
||||
"name": "Décharge de foudre",
|
||||
"rank": 1,
|
||||
"type": "precision",
|
||||
"cost": 3,
|
||||
"speed": "action",
|
||||
"elements": [
|
||||
"thunder"
|
||||
],
|
||||
"effect": "Faites un jet d'attaque avec la [[1. Entrainement#La dextérité|dextérité]]. Tire une décharge foudroyante d'énergie, infligeant 4d4[[2. Glossaire#Jet explosif|!]] dégâts de foudre.",
|
||||
"tags": [
|
||||
"Dégats"
|
||||
],
|
||||
"id": "9"
|
||||
},
|
||||
{
|
||||
"name": "No name",
|
||||
"rank": 1,
|
||||
"type": "precision",
|
||||
"cost": 2,
|
||||
"speed": "action",
|
||||
"elements": [
|
||||
"earth"
|
||||
],
|
||||
"effect": "Faites un jet d'attaque avec la [[1. Entrainement#La dextérité|dextérité]]. Un pilier de matière est extirpé du sol pour aller frapper la cible, qui est alors déplacée d'une case. Si la cible est propulsée contre un mur, elle subit alors 3d12 dégâts contondant.",
|
||||
"tags": [
|
||||
"Dégats"
|
||||
],
|
||||
"id": "10"
|
||||
},
|
||||
{
|
||||
"name": "No name",
|
||||
"rank": 1,
|
||||
"type": "precision",
|
||||
"cost": 3,
|
||||
"speed": "action",
|
||||
"elements": [
|
||||
"earth"
|
||||
],
|
||||
"effect": "Faites un jet d'attaque avec la [[1. Entrainement#La dextérité|dextérité]]. Propulse un projectile de matière sur la cible, infligeant 1d12 dégâts contondant en touchant, ainsi qu'un [[3. Résistance aux chocs#Le jet de résistance|jet de résistance]] (d10/5 + mod. d'[[1. Entrainement#L'intelligence|intelligence]]) à l'[[2. Liste des effets#L'hébètement|hébètement]].",
|
||||
"tags": [
|
||||
"Debuff"
|
||||
],
|
||||
"id": "11"
|
||||
},
|
||||
{
|
||||
"name": "Bouclier tortue",
|
||||
"rank": 1,
|
||||
"type": "knowledge",
|
||||
"cost": 3,
|
||||
"speed": "action",
|
||||
"elements": [
|
||||
"earth"
|
||||
],
|
||||
"effect": "Durant 1 minute, vous gagnez un bonus de 2 au blocage, mais subissez également un malus de 2 à l'esquive et perdez 2 cases de vitesse de course.",
|
||||
"tags": [
|
||||
"Tank"
|
||||
],
|
||||
"id": "12"
|
||||
},
|
||||
{
|
||||
"name": "No name",
|
||||
"rank": 1,
|
||||
"type": "instinct",
|
||||
"cost": 3,
|
||||
"speed": "reaction",
|
||||
"elements": [
|
||||
"earth"
|
||||
],
|
||||
"effect": "Vous gagnez une résistance aux dégâts physiques jusqu'au début de votre prochain tour.",
|
||||
"tags": [
|
||||
"Tank"
|
||||
],
|
||||
"id": "13"
|
||||
},
|
||||
{
|
||||
"name": "Enchantement mineur",
|
||||
"rank": 1,
|
||||
"type": "knowledge",
|
||||
"cost": 2,
|
||||
"speed": "action",
|
||||
"elements": [
|
||||
"arcana"
|
||||
],
|
||||
"effect": "Condense de l'énergie magique dans une arme ou un projectile sur vous. Vous faites une attaque immédiatement après avoir lancé ce sort sans dépenser d'action, infligeant 1d8 dégâts supplémentaire. Les dégâts de l'arme deviennent magique.",
|
||||
"tags": [
|
||||
"Buff"
|
||||
],
|
||||
"id": "14"
|
||||
},
|
||||
{
|
||||
"name": "Rupture de force",
|
||||
"rank": 1,
|
||||
"type": "knowledge",
|
||||
"cost": 5,
|
||||
"speed": "action",
|
||||
"elements": [
|
||||
"arcana"
|
||||
],
|
||||
"effect": "Faites un jet d'attaque avec l'[[1. Entrainement#L'intelligence|intelligence]]. Vous condensez une puissante énergie magique qui est propulsée directement sur votre cible. Vous lancez 2d20 et prenez le plus haut résultat pour infliger des dégâts magique. *Avoir un [[2. Glossaire#Avantage et désavantage|avantage]] **aux dégâts** permet de lancer un autre d20.* *Augmenter les dégâts de ce sort permet d'infliger 5 dégâts magique supplémentaire.*",
|
||||
"tags": [
|
||||
"Dégats"
|
||||
],
|
||||
"id": "15"
|
||||
},
|
||||
{
|
||||
"name": "Foulée aérienne",
|
||||
"rank": 1,
|
||||
"type": "knowledge",
|
||||
"cost": 3,
|
||||
"speed": "action",
|
||||
"elements": [
|
||||
"air"
|
||||
],
|
||||
"effect": "La vitesse de course de votre cible augmente de 2 cases pendant 1 minute. Elle gagne également un bonus de +1 à l'esquive.",
|
||||
"tags": [
|
||||
"Buff"
|
||||
],
|
||||
"id": "16"
|
||||
},
|
||||
{
|
||||
"name": "Pression forcée",
|
||||
"rank": 1,
|
||||
"type": "precision",
|
||||
"cost": 5,
|
||||
"speed": "action",
|
||||
"elements": [
|
||||
"air"
|
||||
],
|
||||
"effect": "Crée une imposante colonne d'air descendent de 3 cases de rayon sur 12 cases de haut à 18 cases de vous. Les créatures à l'intérieur ont un malus de 1 à l'esquive. Les créatures volantes chutent de 3 cases par tour. Dure 5 tours.",
|
||||
"tags": [
|
||||
"Mouvement"
|
||||
],
|
||||
"id": "17"
|
||||
},
|
||||
{
|
||||
"name": "Poids plume",
|
||||
"rank": 1,
|
||||
"type": "knowledge",
|
||||
"cost": 2,
|
||||
"speed": "action",
|
||||
"elements": [
|
||||
"air"
|
||||
],
|
||||
"effect": "Réduit le poids d'un objet à un dixième de son poids d'origine pendant 1 minute. Fonctionne sur des objets inertes allant jusqu'à 500kg. ",
|
||||
"tags": [
|
||||
"Utilitaire"
|
||||
],
|
||||
"id": "18"
|
||||
},
|
||||
{
|
||||
"name": "Conservation",
|
||||
"rank": 1,
|
||||
"type": "knowledge",
|
||||
"cost": 2,
|
||||
"speed": 1,
|
||||
"elements": [
|
||||
"nature"
|
||||
],
|
||||
"effect": "Permet à jusqu'à 5 herbes ou préparations médicinales de se conserver 1 jour de plus. *Ne peux être utilisé qu'une seule fois par herbe/préparation.*",
|
||||
"tags": [
|
||||
"Utilitaire"
|
||||
],
|
||||
"id": "19"
|
||||
},
|
||||
{
|
||||
"name": "No name",
|
||||
"rank": 1,
|
||||
"type": "instinct",
|
||||
"cost": 3,
|
||||
"speed": "action",
|
||||
"elements": [
|
||||
"nature"
|
||||
],
|
||||
"effect": "Vous récupérez un point de fatigue temporaire de la cible que vous touchez.",
|
||||
"tags": [
|
||||
"Support"
|
||||
],
|
||||
"id": "20"
|
||||
},
|
||||
{
|
||||
"name": "No name",
|
||||
"rank": 1,
|
||||
"type": "precision",
|
||||
"cost": 3,
|
||||
"speed": "action",
|
||||
"elements": [
|
||||
"nature"
|
||||
],
|
||||
"effect": "Faites un jet d'attaque avec la [[1. Entrainement#La dextérité|dextérité]]. Inflige 2d8+2 dégâts magique à l'armure de la cible.",
|
||||
"tags": [
|
||||
"Dégats"
|
||||
],
|
||||
"id": "21"
|
||||
},
|
||||
{
|
||||
"name": "Absorption radieuse",
|
||||
"rank": 1,
|
||||
"type": "knowledge",
|
||||
"cost": 3,
|
||||
"speed": "action",
|
||||
"elements": [
|
||||
"light"
|
||||
],
|
||||
"effect": "Absorbe la lumière d'une zone de 4 cases de rayon, la faisant apparaitre comme plus sombre durant 1 minute. ",
|
||||
"tags": [
|
||||
"Support"
|
||||
],
|
||||
"id": "22"
|
||||
},
|
||||
{
|
||||
"name": "Orbe de lumière",
|
||||
"rank": 1,
|
||||
"type": "knowledge",
|
||||
"cost": 2,
|
||||
"speed": "action",
|
||||
"elements": [
|
||||
"light"
|
||||
],
|
||||
"effect": "Fait apparaitre une boule de lumière immatérielle illuminant d'une lumière visible à 12 cases. Peut être bougée de 6 cases avec une action libre.",
|
||||
"tags": [
|
||||
"Utilitaire"
|
||||
],
|
||||
"id": "23"
|
||||
},
|
||||
{
|
||||
"name": "Pas des ombres",
|
||||
"rank": 1,
|
||||
"type": "instinct",
|
||||
"cost": 4,
|
||||
"speed": "action",
|
||||
"elements": [
|
||||
"light"
|
||||
],
|
||||
"effect": "Si vous êtes dans une zone de noir total, vous pouvez vous téléporter dans n'importe quelle autre zone de noir total à 9 cases.",
|
||||
"tags": [
|
||||
"Mouvement"
|
||||
],
|
||||
"id": "24"
|
||||
},
|
||||
{
|
||||
"name": "No name",
|
||||
"rank": 1,
|
||||
"type": "instinct",
|
||||
"cost": 6,
|
||||
"speed": "action",
|
||||
"elements": [
|
||||
"psyche"
|
||||
],
|
||||
"effect": "Envenime l'esprit de la cible, brouillant sa perception de la réalité et lui faisant voir des images subliminales de chaos. La cible fait un [[3. Résistance aux chocs#Le jet de résistance|jet de résistance]] (d8/4 + mod. de psyché) à la [[2. Liste des effets#Apeuré|peur]].",
|
||||
"tags": [
|
||||
"Debuff"
|
||||
],
|
||||
"id": "25"
|
||||
},
|
||||
{
|
||||
"name": "Boule de feu",
|
||||
"rank": 2,
|
||||
"type": "precision",
|
||||
"cost": 8,
|
||||
"speed": "action",
|
||||
"elements": [
|
||||
"fire"
|
||||
],
|
||||
"effect": "Lance une boule de feu éclatant sur 4 cases de rayon. Chaque personne dans le rayon doit faire un [[3. Résistance aux chocs#Le jet de résistance|jet de résistance]] (d10/6 + mod. d'[[1. Entrainement#L'intelligence|intelligence]]) ou subit 3d10 dégâts de feu.",
|
||||
"tags": [
|
||||
"Dégats"
|
||||
],
|
||||
"id": "26"
|
||||
},
|
||||
{
|
||||
"name": "No name",
|
||||
"rank": 2,
|
||||
"type": "knowledge",
|
||||
"cost": 5,
|
||||
"speed": "action",
|
||||
"elements": [
|
||||
"earth"
|
||||
],
|
||||
"effect": "Fait apparaitre une myriade de petites pierres flottantes qui forment une ligne de 6 cases de long pour 3 lignes de haut. Tout le monde peut passer au travers mais les projectiles et sorts de précisions qui le traversent voit leur dé de dégâts réduit de 1 niveau. %% Important, pas de limite de durée %%",
|
||||
"tags": [
|
||||
"Support"
|
||||
],
|
||||
"id": "27"
|
||||
},
|
||||
{
|
||||
"name": "No name",
|
||||
"rank": 2,
|
||||
"type": "precision",
|
||||
"cost": 4,
|
||||
"speed": "action",
|
||||
"elements": [
|
||||
"earth"
|
||||
],
|
||||
"effect": "Durant 1 minute, vos [[4. Équipement#Les armes naturelles|armes naturelles]] se recouvrent de roches, infligeant des dégâts supplémentaires égal à votre mod. d'intelligence. A chaque coup porté (réussi comme raté), les dégâts décroient d'un point jusqu'à arrivée à 0.",
|
||||
"tags": [
|
||||
"Buff"
|
||||
],
|
||||
"id": "28"
|
||||
},
|
||||
{
|
||||
"name": "No name",
|
||||
"rank": 2,
|
||||
"type": "instinct",
|
||||
"cost": 5,
|
||||
"speed": "action",
|
||||
"elements": [
|
||||
"arcana"
|
||||
],
|
||||
"effect": "Votre cible doit faire un [[3. Résistance aux chocs#Le jet de résistance|jet de résistance]] (d4/3 + mod. d'[[1. Entrainement#L'intelligence|intelligence]]) à l'[[2. Liste des effets#Influencé|influence]]. En cas d'échec, elle perds 2d4[[2. Glossaire#Jet explosif|!]] mana.",
|
||||
"tags": [
|
||||
"Debuff"
|
||||
],
|
||||
"id": "29"
|
||||
},
|
||||
{
|
||||
"name": "Enchantement dense",
|
||||
"rank": 2,
|
||||
"type": "knowledge",
|
||||
"cost": 3,
|
||||
"speed": "action",
|
||||
"elements": [
|
||||
"arcana"
|
||||
],
|
||||
"effect": "Condense de l'énergie magique dans toutes les arme ou projectiles sur vous. Vous faites une attaque immédiatement après avoir lancé ce sort sans dépenser d'action, avec chaque arme infligeant 1d8 dégâts supplémentaire. Les dégâts de l'arme deviennent magique.",
|
||||
"tags": [
|
||||
"Buff"
|
||||
],
|
||||
"id": "30"
|
||||
},
|
||||
{
|
||||
"name": "Enchantement tenace",
|
||||
"rank": 2,
|
||||
"type": "knowledge",
|
||||
"cost": 4,
|
||||
"speed": "action",
|
||||
"elements": [
|
||||
"arcana"
|
||||
],
|
||||
"effect": "Condense de l'énergie magique dans une arme sur vous *jusqu'à la fin de votre prochain tour*. Vous faites une attaque immédiatement après avoir lancé ce sort sans dépenser d'action, infligeant 1d8 dégâts supplémentaire. Les dégâts de l'arme deviennent magique.",
|
||||
"tags": [
|
||||
"Buff"
|
||||
],
|
||||
"id": "31"
|
||||
},
|
||||
{
|
||||
"name": "No name",
|
||||
"rank": 2,
|
||||
"type": "knowledge",
|
||||
"cost": 7,
|
||||
"speed": "action",
|
||||
"elements": [
|
||||
"air"
|
||||
],
|
||||
"effect": "Vous générez un vent chaotique dans un cylindre de 4 cases de rayon sur 6 cases de hauteur pendant 1 minute. Toute personne dans la zone doit se déplacer une fois par tour pour contrebalancer les puissantes rafales ou subira un malus de -2 à ces jets (hors [[1. Magie#Les sorts instinctif|sort d'instinct]]).",
|
||||
"tags": [
|
||||
"Debuff"
|
||||
],
|
||||
"id": "32"
|
||||
},
|
||||
{
|
||||
"name": "No name",
|
||||
"rank": 2,
|
||||
"type": "precision",
|
||||
"cost": 4,
|
||||
"speed": "action",
|
||||
"elements": [
|
||||
"air"
|
||||
],
|
||||
"effect": "Vous bénissez temporairement un arc avec la magie des vents pour les 3 prochaines attaques. Les flèches tirée par cet arc ont une vélocité accrue, les portée sont doublée et vous avez un bonus de +2 pour toucher à moyenne distance.",
|
||||
"tags": [
|
||||
"Buff"
|
||||
],
|
||||
"id": "33"
|
||||
},
|
||||
{
|
||||
"name": "No name",
|
||||
"rank": 2,
|
||||
"type": "precision",
|
||||
"cost": 5,
|
||||
"speed": "action",
|
||||
"elements": [
|
||||
"air"
|
||||
],
|
||||
"effect": "Choisissez une cible volante visible à portée. Votre cible doit faire un [[3. Résistance aux chocs#Le jet de résistance|jet de résistance]] (d10/6 + mod. de [[1. Entrainement#La dextérité|dextérité]]) aux [[1. Magie#Les sorts de précision|sorts de précision]]. En cas d'échec, elle voit sa vitesse de vol réduite de 12 cases.",
|
||||
"tags": [
|
||||
"Mouvement"
|
||||
],
|
||||
"id": "34"
|
||||
},
|
||||
{
|
||||
"name": "No name",
|
||||
"rank": 2,
|
||||
"type": "instinct",
|
||||
"cost": 4,
|
||||
"speed": "reaction",
|
||||
"elements": [
|
||||
"air"
|
||||
],
|
||||
"effect": "Vous pouvez lancer ce sort lorsque vous êtes ciblé par une attaque au corps à corps. Faites un jet de [[1. Magie#Les sorts instinctif|sort instinctif]], si vous faites un meilleur score que l'attaque de votre attaquant, vous lui faites rater son attaque. Cependant, si vous ne parvenez pas à bloquer son attaque, il gagne un niveau de dé de dégâts sur son attaque. %% À vérifier %%",
|
||||
"tags": [
|
||||
"Tank"
|
||||
],
|
||||
"id": "35"
|
||||
},
|
||||
{
|
||||
"name": "No name",
|
||||
"rank": 2,
|
||||
"type": "knowledge",
|
||||
"cost": 6,
|
||||
"speed": "action",
|
||||
"elements": [
|
||||
"nature"
|
||||
],
|
||||
"effect": "Votre cible doit faire un [[3. Résistance aux chocs#Le jet de résistance|jet de résistance]] (d8/5 + mod. d'[[1. Entrainement#L'intelligence|intelligence]]) aux [[1. Magie#Les sorts de savoir|sorts de savoir]]. En cas d'échec, elle subit un point de fatigue temporaire.",
|
||||
"tags": [
|
||||
"Debuff"
|
||||
],
|
||||
"id": "36"
|
||||
},
|
||||
{
|
||||
"name": "No name",
|
||||
"rank": 2,
|
||||
"type": "instinct",
|
||||
"cost": 5,
|
||||
"speed": "reaction",
|
||||
"elements": [
|
||||
"nature"
|
||||
],
|
||||
"effect": "Vous récupérez un point de fatigue persistante de votre cible.",
|
||||
"tags": [
|
||||
"Support"
|
||||
],
|
||||
"id": "37"
|
||||
},
|
||||
{
|
||||
"name": "No name",
|
||||
"rank": 2,
|
||||
"type": "knowledge",
|
||||
"cost": 4,
|
||||
"speed": "action",
|
||||
"elements": [
|
||||
"light"
|
||||
],
|
||||
"effect": "Vous gagnez pendant 1 minute une vision dans le noir à 12 cases.",
|
||||
"tags": [
|
||||
"Utilitaire"
|
||||
],
|
||||
"id": "38"
|
||||
},
|
||||
{
|
||||
"name": "Poussière incandescente",
|
||||
"rank": 2,
|
||||
"type": "knowledge",
|
||||
"cost": 5,
|
||||
"speed": "action",
|
||||
"elements": [
|
||||
"light"
|
||||
],
|
||||
"effect": "Crée une zone de poussière brulante de 6 cases de rayon émettant une lumière intense durant 1 minute. Finir son tour dans la poussière vous fait subir 1d12 dégâts de feu. #rework",
|
||||
"tags": [
|
||||
"Dégats"
|
||||
],
|
||||
"id": "39"
|
||||
},
|
||||
{
|
||||
"name": "Apaisement",
|
||||
"rank": 2,
|
||||
"type": "knowledge",
|
||||
"cost": 3,
|
||||
"speed": "action",
|
||||
"elements": [
|
||||
"psyche"
|
||||
],
|
||||
"effect": "En touchant la cible, guérit l'influence, le charme et la peur, mais inflige un malus de -1 aux jets de résistance de défense pour ces effets.",
|
||||
"tags": [
|
||||
"Support"
|
||||
],
|
||||
"id": "40"
|
||||
},
|
||||
{
|
||||
"name": "Painshock",
|
||||
"rank": 2,
|
||||
"type": "instinct",
|
||||
"cost": 6,
|
||||
"speed": "action",
|
||||
"elements": [
|
||||
"psyche"
|
||||
],
|
||||
"effect": "*Ne fonctionne que si la cible touchée à subit des dégâts depuis votre dernier tour.* Vous touchez une plaie et intensifiez la douleur à l'extrême. La cible doit faire un [[3. Résistance aux chocs#Le jet de résistance|jet de résistance]] (d10/5 + mod. d'[[1. Entrainement#L'intelligence|intelligence]] + 1 par 10% de vie perdu au tour précédent) à l'[[2. Liste des effets#L'hébètement|hébètement]]. ",
|
||||
"tags": [
|
||||
"Debuff"
|
||||
],
|
||||
"id": "41"
|
||||
},
|
||||
{
|
||||
"name": "Perturbateur",
|
||||
"rank": 2,
|
||||
"type": "instinct",
|
||||
"cost": 4,
|
||||
"speed": "reaction",
|
||||
"elements": [
|
||||
"psyche"
|
||||
],
|
||||
"effect": "Vous pouvez perturber les flux magiques d'un lanceur de sort que vous voyez à 9 cases pour lui imposer un malus de 3 à son lancer de sort en cours.",
|
||||
"tags": [
|
||||
"Debuff"
|
||||
],
|
||||
"id": "42"
|
||||
},
|
||||
{
|
||||
"name": "Tourbillon de braise",
|
||||
"rank": 3,
|
||||
"type": "knowledge",
|
||||
"cost": 6,
|
||||
"speed": "action",
|
||||
"elements": [
|
||||
"fire"
|
||||
],
|
||||
"effect": "Fait apparaitre une tornade de braises ardente de 2 cases de rayon. Chaque tour, vous pouvez la faire bouger de 2 cases pour 1 point d'action. Toute personne commençant son tour dans la tornade subit 2d8 dégâts de feu.",
|
||||
"tags": [
|
||||
"Dégats"
|
||||
],
|
||||
"id": "43"
|
||||
},
|
||||
{
|
||||
"name": "Engourdissement",
|
||||
"rank": 3,
|
||||
"type": "instinct",
|
||||
"cost": 5,
|
||||
"speed": "action",
|
||||
"elements": [
|
||||
"ice"
|
||||
],
|
||||
"effect": "La cible doit faire un [[3. Résistance aux chocs#Le jet de résistance|jet de résistance]] (d10/6 + mod. de [[1. Entrainement#La psyché|psyché]]) aux [[1. Magie#Les sorts instinctif|sorts d'instinct]], divisant sa vitesse par 2 et lui imposant un malus de 3 pour attaquer avec des armes en cas d'échec.",
|
||||
"tags": [
|
||||
"Debuff"
|
||||
],
|
||||
"id": "44"
|
||||
},
|
||||
{
|
||||
"name": "Orbe de chaos",
|
||||
"rank": 3,
|
||||
"type": "precision",
|
||||
"cost": 9,
|
||||
"speed": "action",
|
||||
"elements": [
|
||||
"thunder"
|
||||
],
|
||||
"effect": "Fait apparaitre une orbe de foudre d'une case. Chaque tour pendant 1 minute, à l'initiative de l'environnement, l'orbe lance un d4 pour choisir un point cardinal. Chaque personne dans un cône de 6 cases (90°) dans cette direction doit faire un [[3. Résistance aux chocs#Le jet de résistance|jet de résistance]] (d12/7 + mod. d'[[1. Entrainement#L'intelligence|intelligence]]) aux [[1. Magie#Les sorts de précision|sorts de précision]] ou subit 6d6[[2. Glossaire#Jet explosif|!]] dégâts de foudre.",
|
||||
"tags": [
|
||||
"Dégats"
|
||||
],
|
||||
"id": "45"
|
||||
},
|
||||
{
|
||||
"name": "Rejet pur",
|
||||
"rank": 3,
|
||||
"type": "knowledge",
|
||||
"cost": null,
|
||||
"speed": "action",
|
||||
"elements": [
|
||||
"arcana"
|
||||
],
|
||||
"effect": "Faites un jet d'attaque avec l'[[1. Entrainement#L'intelligence|intelligence]]. Vous propulsez une énergie magique pure condensée sur votre adversaire avec une puissance absolue. Vous infligez 1d6[[2. Glossaire#Jet explosif|!]]+2 dégâts magique par tranche de 3 mana dépensé. Vous pouvez dépenser jusqu'à 30 mana. Vous subissez un malus de 4 au lancer de sort au tour suivant.",
|
||||
"tags": [
|
||||
"Dégats"
|
||||
],
|
||||
"id": "46"
|
||||
},
|
||||
{
|
||||
"name": "Disruption",
|
||||
"rank": 3,
|
||||
"type": "instinct",
|
||||
"cost": 5,
|
||||
"speed": "action",
|
||||
"elements": [
|
||||
"arcana"
|
||||
],
|
||||
"effect": "Faites un jet d'attaque avec la [[1. Entrainement#La psyché|psyché]]. Vous imposez un jet de concentration à une cible que vous voyez. La difficulté est de 4d6+4.",
|
||||
"tags": [
|
||||
"Debuff"
|
||||
],
|
||||
"id": "47"
|
||||
},
|
||||
{
|
||||
"name": "Anomalie immaculée",
|
||||
"rank": 3,
|
||||
"type": "knowledge",
|
||||
"cost": 6,
|
||||
"speed": "action",
|
||||
"elements": [
|
||||
"light"
|
||||
],
|
||||
"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",
|
||||
"tags": [
|
||||
"Dégats"
|
||||
],
|
||||
"id": "48"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -12,11 +12,26 @@ export function unifySlug(slug: string | string[]): string
|
||||
{
|
||||
return (Array.isArray(slug) ? slug.join('/') : slug);
|
||||
}
|
||||
export function group<
|
||||
T,
|
||||
K extends keyof T,
|
||||
V extends keyof T,
|
||||
KeyType extends string | number | symbol = Extract<T[K], string | number | symbol>
|
||||
>(
|
||||
table: T[],
|
||||
key: K & (T[K] extends string | number | symbol ? K : never),
|
||||
value: V
|
||||
): Record<KeyType, T[V]> {
|
||||
return table.reduce((p, v) => {
|
||||
p[v[key] as KeyType] = v[value];
|
||||
return p;
|
||||
}, {} as Record<KeyType, T[V]>);
|
||||
}
|
||||
export function parsePath(path: string): string
|
||||
{
|
||||
return path.toLowerCase().trim().replaceAll(" ", "-").normalize("NFD").replaceAll(/[\u0300-\u036f]/g, "").replaceAll('(', '').replaceAll(')', '');
|
||||
}
|
||||
export function parseId(id: string | undefined): string |undefined
|
||||
export function parseId(id: string | undefined): string | undefined
|
||||
{
|
||||
return id;
|
||||
return id?.normalize('NFD')?.replace(/[\u0300-\u036f]/g, '')?.replace(/^\d\. */g, '')?.replace(/\s/g, "-")?.replace(/%/g, "-percent")?.replace(/\?/g, "-q")?.toLowerCase();
|
||||
|
||||
167
types/character.d.ts
vendored
167
types/character.d.ts
vendored
@@ -1,167 +0,0 @@
|
||||
export type MainStat = "strength" | "dexterity" | "constitution" | "intelligence" | "curiosity" | "charisma" | "psyche";
|
||||
export type Ability = "athletics" | "acrobatics" | "intimidation" | "sleightofhand" | "stealth" | "survival" | "investigation" | "history" | "religion" | "arcana" | "understanding" | "perception" | "performance" | "medecine" | "persuasion" | "animalhandling" | "deception";
|
||||
export type Level = | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20;
|
||||
export type TrainingLevel = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15;
|
||||
export type SpellType = "precision" | "knowledge" | "instinct" | "arts";
|
||||
export type Category = "action" | "reaction" | "freeaction" | "misc";
|
||||
export type Resistance = keyof CompiledCharacter["resistance"];
|
||||
|
||||
export type DoubleIndex<T extends number | string> = [T, number];
|
||||
|
||||
export type Progression = {
|
||||
training: Record<MainStat, DoubleIndex<TrainingLevel>[]>;
|
||||
race: {
|
||||
index?: number;
|
||||
progress?: DoubleIndex<Level>[];
|
||||
};
|
||||
level: number;
|
||||
abilities: Partial<Record<Ability, [number, number]>>; //First is the ability, second is the max increment
|
||||
spells?: string[]; //Spell ID
|
||||
modifiers: Partial<Record<MainStat, number>>;
|
||||
aspect?: string;
|
||||
notes: string;
|
||||
};
|
||||
export type Character = {
|
||||
id: number;
|
||||
name: string;
|
||||
progress: Progression;
|
||||
owner?: number;
|
||||
};
|
||||
export type CharacterConfig = {
|
||||
peoples: Race[],
|
||||
training: Record<MainStat, Record<TrainingLevel, TrainingOption[]>>;
|
||||
abilities: Record<Ability, AbilityConfig>;
|
||||
resistances: Record<Resistance, ResistanceConfig>;
|
||||
};
|
||||
export type AbilityConfig = {
|
||||
max: [MainStat, MainStat];
|
||||
name: string;
|
||||
description: string;
|
||||
};
|
||||
export type ResistanceConfig = {
|
||||
name: string;
|
||||
statistic: MainStat;
|
||||
};
|
||||
export type Race = {
|
||||
name: string;
|
||||
description: string;
|
||||
utils: {
|
||||
maxOption: number;
|
||||
};
|
||||
options: Record<Level, RaceOption[]>;
|
||||
};
|
||||
export type RaceOption = {
|
||||
training?: number;
|
||||
health?: number;
|
||||
mana?: number;
|
||||
shaping?: number;
|
||||
modifier?: number;
|
||||
abilities?: number;
|
||||
};
|
||||
export type Feature = {
|
||||
text?: string;
|
||||
} & (ActionFeature | ReactionFeature | FreeActionFeature | BonusFeature | MiscFeature);
|
||||
type ActionFeature = {
|
||||
type: "action";
|
||||
cost: 1 | 2 | 3;
|
||||
text: string;
|
||||
};
|
||||
type ReactionFeature = {
|
||||
type: "reaction";
|
||||
text: string;
|
||||
};
|
||||
type FreeActionFeature = {
|
||||
type: "freeaction";
|
||||
text: string;
|
||||
};
|
||||
type BonusFeature = {
|
||||
type: "bonus";
|
||||
action: "add" | "remove" | "set" | "cap";
|
||||
value: number;
|
||||
property: string;
|
||||
};
|
||||
type MiscFeature = {
|
||||
type: "misc";
|
||||
text: string;
|
||||
};
|
||||
export type TrainingOption = {
|
||||
description: Array<{
|
||||
text: string;
|
||||
disposable?: boolean;
|
||||
replaced?: boolean;
|
||||
category?: Category;
|
||||
}>;
|
||||
|
||||
//Automatically calculated by compiler
|
||||
mana?: number;
|
||||
health?: number;
|
||||
speed?: false | number;
|
||||
initiative?: number;
|
||||
mastery?: keyof CompiledCharacter["mastery"];
|
||||
spellrank?: SpellType;
|
||||
defense?: Array<keyof CompiledCharacter["defense"]>;
|
||||
resistance?: [Resistance, "attack" | "defense"][];
|
||||
|
||||
//Used during character creation, not used by compiler
|
||||
modifier?: number;
|
||||
ability?: number;
|
||||
spec?: number;
|
||||
spellslot?: number | MainStat;
|
||||
arts?: number | MainStat;
|
||||
|
||||
features?: Feature[]; //TODO
|
||||
};
|
||||
export type CompiledCharacter = {
|
||||
id: number;
|
||||
owner?: number;
|
||||
username?: string;
|
||||
name: string;
|
||||
health: number;
|
||||
mana: number;
|
||||
race: number;
|
||||
spellslots: number;
|
||||
artslots: number;
|
||||
spellranks: Record<SpellType, 0 | 1 | 2 | 3>;
|
||||
aspect: string;
|
||||
speed: number | false;
|
||||
initiative: number;
|
||||
|
||||
defense: {
|
||||
static: number;
|
||||
activeparry: number;
|
||||
activedodge: number;
|
||||
passiveparry: number;
|
||||
passivedodge: number;
|
||||
};
|
||||
|
||||
mastery: {
|
||||
strength: number;
|
||||
dexterity: number;
|
||||
shield: number;
|
||||
armor: number;
|
||||
multiattack: number;
|
||||
magicpower: number;
|
||||
magicspeed: number;
|
||||
magicelement: number;
|
||||
};
|
||||
|
||||
resistance: { //First is attack, second is defense
|
||||
stun: [number, number];
|
||||
bleed: [number, number];
|
||||
poison: [number, number];
|
||||
fear: [number, number];
|
||||
influence: [number, number];
|
||||
charm: [number, number];
|
||||
possesion: [number, number];
|
||||
precision: [number, number];
|
||||
knowledge: [number, number];
|
||||
instinct: [number, number];
|
||||
};
|
||||
|
||||
modifier: Record<MainStat, number>;
|
||||
abilities: Partial<Record<Ability, number>>;
|
||||
level: number;
|
||||
features: Record<Category, string[]>; //Currently: List of training option as text. TODO: Update to a more complex structure later
|
||||
|
||||
notes: string;
|
||||
};
|
||||
252
types/character.ts
Normal file
252
types/character.ts
Normal file
@@ -0,0 +1,252 @@
|
||||
import { z, type ZodRawShape } from "zod";
|
||||
import { characterTable } from "~/db/schema";
|
||||
|
||||
export const MAIN_STATS = ["strength","dexterity","constitution","intelligence","curiosity","charisma","psyche"] as const; export type MainStat = typeof MAIN_STATS[number];
|
||||
export const ABILITIES = ["athletics","acrobatics","intimidation","sleightofhand","stealth","survival","investigation","history","religion","arcana","understanding","perception","performance","medecine","persuasion","animalhandling","deception"] as const; export type Ability = typeof ABILITIES[number];
|
||||
export const LEVELS = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20] as const; export type Level = typeof LEVELS[number];
|
||||
export const TRAINING_LEVELS = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15] as const; export type TrainingLevel = typeof TRAINING_LEVELS[number];
|
||||
export const SPELL_TYPES = ["precision","knowledge","instinct","arts"] as const; export type SpellType = typeof SPELL_TYPES[number];
|
||||
export const CATEGORIES = ["action","reaction","freeaction","misc"] as const; export type Category = typeof CATEGORIES[number];
|
||||
export const SPELL_ELEMENTS = ["fire","ice","thunder","earth","arcana","air","nature","light","psyche"] as const; export type SpellElement = typeof SPELL_ELEMENTS[number];
|
||||
export const RESISTANCES = ["stun","bleed","poison","fear","influence","charm","possesion","precision","knowledge","instinct"] as const; export type Resistance = typeof RESISTANCES[number];
|
||||
|
||||
export type DoubleIndex<T extends number | string> = [T, number];
|
||||
|
||||
export const defaultCharacter: Character = {
|
||||
id: -1,
|
||||
|
||||
name: "",
|
||||
people: undefined,
|
||||
level: 1,
|
||||
health: 0,
|
||||
mana: 0,
|
||||
|
||||
training: MAIN_STATS.reduce((p, v) => { p[v] = [[0, 0]]; return p; }, {} as Record<MainStat, DoubleIndex<TrainingLevel>[]>),
|
||||
leveling: [[1, 0]],
|
||||
abilities: {},
|
||||
spells: [],
|
||||
modifiers: {},
|
||||
|
||||
owner: -1,
|
||||
visibility: "private",
|
||||
};
|
||||
export const mainStatTexts: Record<MainStat, string> = {
|
||||
"strength": "Force",
|
||||
"dexterity": "Dextérité",
|
||||
"constitution": "Constitution",
|
||||
"intelligence": "Intelligence",
|
||||
"curiosity": "Curiosité",
|
||||
"charisma": "Charisme",
|
||||
"psyche": "Psyché",
|
||||
}
|
||||
export const elementTexts: Record<SpellElement, { class: string, text: string }> = {
|
||||
fire: { class: 'text-light-red dark:text-dark-red', text: 'Feu' },
|
||||
ice: { class: 'text-light-blue dark:text-dark-blue', text: 'Glace' },
|
||||
thunder: { class: 'text-light-yellow dark:text-dark-yellow', text: 'Foudre' },
|
||||
earth: { class: 'text-light-orange dark:text-dark-orange', text: 'Terre' },
|
||||
arcana: { class: 'text-light-purple dark:text-dark-purple', text: 'Arcane' },
|
||||
air: { class: 'text-light-green dark:text-dark-green', text: 'Air' },
|
||||
nature: { class: 'text-light-green dark:text-dark-green', text: 'Nature' },
|
||||
light: { class: 'text-light-yellow dark:text-dark-yellow', text: 'Lumière' },
|
||||
psyche: { class: 'text-light-purple dark:text-dark-purple', text: 'Psy' },
|
||||
}
|
||||
export const spellTypeTexts: Record<SpellType, string> = { "instinct": "Instinct", "knowledge": "Savoir", "precision": "Précision", "arts": "Oeuvres" };
|
||||
|
||||
export const CharacterValidation = z.object({
|
||||
id: z.number(),
|
||||
name: z.string(),
|
||||
people: z.number().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.object(MAIN_STATS.reduce((p, v) => {
|
||||
p[v] = z.array(z.tuple([z.number().min(0).max(15), z.number()]));
|
||||
return p;
|
||||
}, {} as Record<MainStat, z.ZodArray<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>>)),
|
||||
leveling: z.array(z.tuple([z.number().min(1).max(20), z.number()])),
|
||||
abilities: z.object(ABILITIES.reduce((p, v) => {
|
||||
p[v] = z.tuple([z.number(), z.number()]);
|
||||
return p;
|
||||
}, {} as Record<Ability, z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>)).partial(),
|
||||
spells: z.string().array(),
|
||||
modifiers: z.object(MAIN_STATS.reduce((p, v) => {
|
||||
p[v] = z.number();
|
||||
return p;
|
||||
}, {} as Record<MainStat, z.ZodNumber>)).partial(),
|
||||
owner: z.number(),
|
||||
username: z.string().optional(),
|
||||
visibility: z.enum(["public", "private"]),
|
||||
thumbnail: z.any(),
|
||||
})
|
||||
export type Character = {
|
||||
id: number;
|
||||
|
||||
name: string;
|
||||
people?: number;
|
||||
level: number;
|
||||
aspect?: number | null;
|
||||
notes?: string | null;
|
||||
health: number;
|
||||
mana: number;
|
||||
|
||||
training: Record<MainStat, DoubleIndex<TrainingLevel>[]>;
|
||||
leveling: DoubleIndex<Level>[];
|
||||
abilities: Partial<Record<Ability, [number, number]>>; //First is the ability, second is the max increment
|
||||
spells: string[]; //Spell ID
|
||||
modifiers: Partial<Record<MainStat, number>>;
|
||||
|
||||
owner: number;
|
||||
username?: string;
|
||||
visibility: "private" | "public";
|
||||
};
|
||||
export type CharacterValues = {
|
||||
health: number;
|
||||
mana: number;
|
||||
};
|
||||
export type CharacterConfig = {
|
||||
peoples: Race[],
|
||||
training: Record<MainStat, Record<TrainingLevel, TrainingOption[]>>;
|
||||
abilities: Record<Ability, AbilityConfig>;
|
||||
resistances: Record<Resistance, ResistanceConfig>;
|
||||
spells: SpellConfig[];
|
||||
};
|
||||
export type SpellConfig = {
|
||||
id: string;
|
||||
name: string;
|
||||
rank: 1 | 2 | 3;
|
||||
type: SpellType;
|
||||
cost: number;
|
||||
speed: "action" | "reaction" | number;
|
||||
elements: Array<SpellElement>;
|
||||
effect: string;
|
||||
tags?: string[];
|
||||
};
|
||||
export type AbilityConfig = {
|
||||
max: [MainStat, MainStat];
|
||||
name: string;
|
||||
description: string;
|
||||
};
|
||||
export type ResistanceConfig = {
|
||||
name: string;
|
||||
statistic: MainStat;
|
||||
};
|
||||
export type Race = {
|
||||
name: string;
|
||||
description: string;
|
||||
utils: {
|
||||
maxOption: number;
|
||||
};
|
||||
options: Record<Level, RaceOption[]>;
|
||||
};
|
||||
export type RaceOption = {
|
||||
training?: number;
|
||||
health?: number;
|
||||
mana?: number;
|
||||
shaping?: number;
|
||||
modifier?: number;
|
||||
abilities?: number;
|
||||
spellslots?: number;
|
||||
};
|
||||
export type Feature = {
|
||||
text?: string;
|
||||
} & (ActionFeature | ReactionFeature | FreeActionFeature | BonusFeature | MiscFeature);
|
||||
type ActionFeature = {
|
||||
type: "action";
|
||||
cost: 1 | 2 | 3;
|
||||
text: string;
|
||||
};
|
||||
type ReactionFeature = {
|
||||
type: "reaction";
|
||||
text: string;
|
||||
};
|
||||
type FreeActionFeature = {
|
||||
type: "freeaction";
|
||||
text: string;
|
||||
};
|
||||
type BonusFeature = {
|
||||
type: "bonus";
|
||||
action: "add" | "remove" | "set" | "cap";
|
||||
value: number;
|
||||
property: string;
|
||||
};
|
||||
type MiscFeature = {
|
||||
type: "misc";
|
||||
text: string;
|
||||
};
|
||||
export type TrainingOption = {
|
||||
description: Array<{
|
||||
text: string;
|
||||
disposable?: boolean;
|
||||
replaced?: boolean;
|
||||
category?: Category;
|
||||
}>;
|
||||
|
||||
//Automatically calculated by compiler
|
||||
mana?: number;
|
||||
health?: number;
|
||||
speed?: false | number;
|
||||
initiative?: number;
|
||||
mastery?: keyof CompiledCharacter["mastery"];
|
||||
spellrank?: SpellType;
|
||||
defense?: Array<keyof CompiledCharacter["defense"]>;
|
||||
resistance?: [Resistance, "attack" | "defense"][];
|
||||
spell?: string;
|
||||
|
||||
//Used during character creation, not used by compiler
|
||||
modifier?: number;
|
||||
ability?: number;
|
||||
spec?: number;
|
||||
spellslot?: number | MainStat;
|
||||
arts?: number | MainStat;
|
||||
|
||||
features?: Feature[]; //TODO
|
||||
};
|
||||
export type CompiledCharacter = {
|
||||
id: number;
|
||||
owner?: number;
|
||||
username?: string;
|
||||
name: string;
|
||||
health: number;
|
||||
mana: number;
|
||||
race: number;
|
||||
spellslots: number;
|
||||
artslots: number;
|
||||
spellranks: Record<SpellType, 0 | 1 | 2 | 3>;
|
||||
aspect: string;
|
||||
speed: number | false;
|
||||
initiative: number;
|
||||
spells: string[];
|
||||
|
||||
values: CharacterValues,
|
||||
|
||||
defense: {
|
||||
static: number;
|
||||
activeparry: number;
|
||||
activedodge: number;
|
||||
passiveparry: number;
|
||||
passivedodge: number;
|
||||
};
|
||||
|
||||
mastery: {
|
||||
strength: number;
|
||||
dexterity: number;
|
||||
shield: number;
|
||||
armor: number;
|
||||
multiattack: number;
|
||||
magicpower: number;
|
||||
magicspeed: number;
|
||||
magicelement: number;
|
||||
};
|
||||
|
||||
//First is attack, second is defense
|
||||
resistance: Record<Resistance, [number, number]>;
|
||||
|
||||
modifier: Record<MainStat, number>;
|
||||
abilities: Partial<Record<Ability, number>>;
|
||||
level: number;
|
||||
features: Record<Category, string[]>; //Currently: List of training option as text. TODO: Update to a more complex structure later
|
||||
|
||||
notes: string;
|
||||
};
|
||||
Reference in New Issue
Block a user