obsidian-visualiser/types/character.ts

248 lines
8.5 KiB
TypeScript

import { z, type ZodRawShape } from "zod/v4";
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 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 border-light-red dark:border-dark-red bg-light-red dark:bg-dark-red', text: 'Feu' },
ice: { class: 'text-light-blue dark:text-dark-blue border-light-blue dark:border-dark-blue bg-light-blue dark:bg-dark-blue', text: 'Glace' },
thunder: { class: 'text-light-yellow dark:text-dark-yellow border-light-yellow dark:border-dark-yellow bg-light-yellow dark:bg-dark-yellow', text: 'Foudre' },
earth: { class: 'text-light-orange dark:text-dark-orange border-light-orange dark:border-dark-orange bg-light-orange dark:bg-dark-orange', text: 'Terre' },
arcana: { class: 'text-light-indigo dark:text-dark-indigo border-light-indigo dark:border-dark-indigo bg-light-indigo dark:bg-dark-indigo', text: 'Arcane' },
air: { class: 'text-light-lime dark:text-dark-lime border-light-lime dark:border-dark-lime bg-light-lime dark:bg-dark-lime', text: 'Air' },
nature: { class: 'text-light-green dark:text-dark-green border-light-green dark:border-dark-green bg-light-green dark:bg-dark-green', text: 'Nature' },
light: { class: 'text-light-yellow dark:text-dark-yellow border-light-yellow dark:border-dark-yellow bg-light-yellow dark:bg-dark-yellow', text: 'Lumière' },
psyche: { class: 'text-light-purple dark:text-dark-purple border-light-purple dark:border-dark-purple bg-light-purple dark:bg-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;
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>;
spells: SpellConfig[];
};
export type SpellConfig = {
id: string;
name: string;
rank: 1 | 2 | 3 | 4;
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 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 FeatureItem = {
category: "misc";
text: string;
} | {
category: "action";
cost: 1 | 2 | 3;
text: string;
} | {
category: "reaction";
cost: 1 | 2;
text: string;
} | {
category: "freeaction";
text: string;
} | {
category: "value";
type: "add" | "remove" | "set";
value: number | false;
property: string;
} | {
category: "asset";
type: "add" | "remove";
kind: "spells";
asset: string;
}
type FeatureCategory = FeatureItem["category"];
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?: Record<MainStat, number>;
bonus?: Record<string, number>;
spell?: string;
//Used during character creation, not used by compiler
modifier?: number;
ability?: number;
spec?: number;
spellslot?: number | MainStat;
arts?: number | MainStat;
features?: FeatureItem[]; //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: {
hardcap: number;
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;
magicinstinct: number;
};
bonus: Record<string, number>; //Any special bonus goes here
resistance: Record<MainStat, number>;
modifier: Record<MainStat, number>;
abilities: Partial<Record<Ability, number>>;
level: number;
features: { [K in FeatureCategory]: Array<Extract<FeatureItem, { category: K }>> };
notes: string;
};