Character BuilderTabs rework
This commit is contained in:
parent
6fe3746df4
commit
5387dc66c3
|
|
@ -192,14 +192,6 @@ export const CharacterValidation = z.object({
|
|||
thumbnail: z.any(),
|
||||
});
|
||||
|
||||
const stepTexts: Record<number, string> = {
|
||||
0: 'Choisissez un peuple afin de définir la progression de votre personnage au fil des niveaux.',
|
||||
1: 'Déterminez la progression de votre personnage en choisissant une option par niveau disponible.',
|
||||
2: 'Spécialisez votre personnage en attribuant vos points d\'entrainement parmi les 7 branches disponibles.\nChaque paliers de 3 points augmentent votre modifieur.',
|
||||
3: 'Diversifiez vos possibilités en affectant vos points dans les différentes compétences disponibles.',
|
||||
4: 'Déterminez l\'Aspect qui vous corresponds et benéficiez de puissants bonus.'
|
||||
};
|
||||
|
||||
type Property = { value: number | string | false, id: string, operation: "set" | "add" };
|
||||
type PropertySum = { list: Array<Property>, value: number, _dirty: boolean };
|
||||
export class CharacterCompiler
|
||||
|
|
@ -400,7 +392,7 @@ export class CharacterBuilder extends CharacterCompiler
|
|||
private _container: HTMLDivElement;
|
||||
private _content?: HTMLDivElement;
|
||||
private _stepsHeader: HTMLDivElement[] = [];
|
||||
private _stepsContent: Array<BuilderTab | (() => BuilderTab)> = [];
|
||||
private _steps: Array<BuilderTabConstructor> = [];
|
||||
private _helperText!: Text;
|
||||
private id?: string;
|
||||
|
||||
|
|
@ -437,34 +429,18 @@ export class CharacterBuilder extends CharacterCompiler
|
|||
}
|
||||
private render()
|
||||
{
|
||||
this._stepsHeader = [
|
||||
dom("div", { class: "group flex items-center", }, [
|
||||
dom("div", { class: "px-2 py-1 border-b border-transparent hover:border-accent-blue disabled:text-light-50 dark:disabled:text-dark-50 disabled:hover:border-transparent group-data-[state=active]:text-accent-blue cursor-pointer", listeners: { click: e => this.display(0) } }, [text("Peuples")]),
|
||||
]),
|
||||
dom("div", { class: "group flex items-center", }, [
|
||||
icon("radix-icons:chevron-right", { class: "w-6 h-6 flex justify-center items-center group-data-[disabled]:text-light-50 dark:group-data-[disabled]:text-dark-50 group-data-[disabled]:hover:border-transparent me-4" }),
|
||||
dom("div", { class: "px-2 py-1 border-b border-transparent hover:border-accent-blue disabled:text-light-50 dark:disabled:text-dark-50 disabled:hover:border-transparent group-data-[state=active]:text-accent-blue cursor-pointer", listeners: { click: e => this.display(1) } }, [text("Niveaux")]),
|
||||
]),
|
||||
dom("div", { class: "group flex items-center", }, [
|
||||
icon("radix-icons:chevron-right", { class: "w-6 h-6 flex justify-center items-center group-data-[disabled]:text-light-50 dark:group-data-[disabled]:text-dark-50 group-data-[disabled]:hover:border-transparent me-4" }),
|
||||
dom("div", { class: "px-2 py-1 border-b border-transparent hover:border-accent-blue disabled:text-light-50 dark:disabled:text-dark-50 disabled:hover:border-transparent group-data-[state=active]:text-accent-blue cursor-pointer", listeners: { click: e => this.display(2) } }, [text("Entrainement")]),
|
||||
]),
|
||||
dom("div", { class: "group flex items-center", }, [
|
||||
icon("radix-icons:chevron-right", { class: "w-6 h-6 flex justify-center items-center group-data-[disabled]:text-light-50 dark:group-data-[disabled]:text-dark-50 group-data-[disabled]:hover:border-transparent me-4" }),
|
||||
dom("div", { class: "px-2 py-1 border-b border-transparent hover:border-accent-blue disabled:text-light-50 dark:disabled:text-dark-50 disabled:hover:border-transparent group-data-[state=active]:text-accent-blue cursor-pointer", listeners: { click: e => this.display(3) } }, [text("Compétences")]),
|
||||
]),
|
||||
dom("div", { class: "group flex items-center", }, [
|
||||
icon("radix-icons:chevron-right", { class: "w-6 h-6 flex justify-center items-center group-data-[disabled]:text-light-50 dark:group-data-[disabled]:text-dark-50 group-data-[disabled]:hover:border-transparent me-4" }),
|
||||
dom("div", { class: "px-2 py-1 border-b border-transparent hover:border-accent-blue disabled:text-light-50 dark:disabled:text-dark-50 disabled:hover:border-transparent group-data-[state=active]:text-accent-blue cursor-pointer", listeners: { click: e => this.display(4) } }, [text("Aspect")])
|
||||
]),
|
||||
];
|
||||
this._stepsContent = [
|
||||
new PeoplePicker(this),
|
||||
() => new LevelPicker(this),
|
||||
() => new TrainingPicker(this),
|
||||
() => new AbilityPicker(this),
|
||||
() => new AspectPicker(this),
|
||||
this._steps = [
|
||||
PeoplePicker,
|
||||
LevelPicker,
|
||||
TrainingPicker,
|
||||
AbilityPicker,
|
||||
AspectPicker,
|
||||
];
|
||||
this._stepsHeader = this._steps.map((e, i) =>
|
||||
dom("div", { class: "group flex items-center", }, [
|
||||
dom("div", { class: "px-2 py-1 border-b border-transparent hover:border-accent-blue disabled:text-light-50 dark:disabled:text-dark-50 disabled:hover:border-transparent group-data-[state=active]:text-accent-blue cursor-pointer", listeners: { click: () => this.display(i) } }, [text(e.name)]),
|
||||
])
|
||||
);
|
||||
this._helperText = text("Choisissez un peuple afin de définir la progression de votre personnage au fil des niveaux.")
|
||||
this._content = dom('div', { class: 'flex-1 outline-none max-w-full w-full overflow-y-auto', attributes: { id: 'characterEditorContainer' } });
|
||||
this._container.appendChild(div('flex flex-1 flex-col justify-start items-center px-8 w-full h-full overflow-y-hidden', [
|
||||
|
|
@ -485,18 +461,15 @@ export class CharacterBuilder extends CharacterCompiler
|
|||
if(step < 0 || step >= this._stepsHeader.length)
|
||||
return;
|
||||
|
||||
if(step !== 0 && this._stepsContent.slice(0, step).some(e => !(e as BuilderTab).validate()))
|
||||
if(step !== 0 && this._steps.slice(0, step).some(e => !e.validate(this)))
|
||||
return;
|
||||
|
||||
this._stepsContent.forEach((e, i, arr) => arr[i] = i <= step ? typeof e === 'function' ? e() : e : e);
|
||||
|
||||
this._stepsHeader.forEach(e => e.setAttribute('data-state', 'inactive'));
|
||||
this._stepsHeader[step]!.setAttribute('data-state', 'active');
|
||||
|
||||
(this._stepsContent[step]! as BuilderTab).update();
|
||||
this._content?.replaceChildren(...(this._stepsContent[step] as BuilderTab)!.dom);
|
||||
this._content?.replaceChildren(...(new this._steps[step]!(this)).dom);
|
||||
|
||||
this._helperText.textContent = stepTexts[step]!;
|
||||
this._helperText.textContent = this._steps[step]!.description;
|
||||
}
|
||||
async save(leave: boolean = true)
|
||||
{
|
||||
|
|
@ -696,25 +669,37 @@ export class PickableFeature
|
|||
return this._content;
|
||||
}
|
||||
}
|
||||
interface BuilderTab {
|
||||
dom: Array<Node | string>;
|
||||
update: () => void;
|
||||
validate: () => boolean;
|
||||
};
|
||||
class PeoplePicker implements BuilderTab
|
||||
{
|
||||
private _builder: CharacterBuilder;
|
||||
private _content: Array<Node | string>;
|
||||
abstract class BuilderTab {
|
||||
protected _builder: CharacterBuilder;
|
||||
protected _content!: Array<Node | string>;
|
||||
static name: string;
|
||||
static description: string;
|
||||
|
||||
constructor(builder: CharacterBuilder) { this._builder = builder; }
|
||||
update() { }
|
||||
static validate(builder: CharacterBuilder): boolean { return false; }
|
||||
get dom() { return this._content; }
|
||||
};
|
||||
type BuilderTabConstructor = {
|
||||
new (builder: CharacterBuilder): BuilderTab;
|
||||
name: string;
|
||||
description: string;
|
||||
validate(builder: CharacterBuilder): boolean;
|
||||
}
|
||||
class PeoplePicker extends BuilderTab
|
||||
{
|
||||
private _nameInput: HTMLInputElement;
|
||||
private _visibilityInput: HTMLDivElement;
|
||||
private _options: HTMLDivElement[];
|
||||
|
||||
private _activeOption?: HTMLDivElement;
|
||||
|
||||
static override name = 'Peuple';
|
||||
static override description = 'Choisissez un peuple afin de définir la progression de votre personnage au fil des niveaux.';
|
||||
|
||||
constructor(builder: CharacterBuilder)
|
||||
{
|
||||
this._builder = builder;
|
||||
super(builder);
|
||||
|
||||
this._nameInput = input('text', {
|
||||
input: (value) => {
|
||||
|
|
@ -746,7 +731,7 @@ class PeoplePicker implements BuilderTab
|
|||
button(text('Suivant'), () => this._builder.display(1), 'h-[35px] px-[15px]'),
|
||||
]), div('flex flex-1 gap-4 p-2 overflow-x-auto justify-center', this._options)];
|
||||
}
|
||||
update()
|
||||
override update()
|
||||
{
|
||||
this._nameInput.value = this._builder.character.name;
|
||||
this._visibilityInput.setAttribute('data-state', this._builder.character.visibility === "private" ? "checked" : "unchecked");
|
||||
|
|
@ -758,29 +743,25 @@ class PeoplePicker implements BuilderTab
|
|||
"border-accent-blue outline-2 outline outline-accent-blue".split(" ").forEach(e => this._activeOption?.classList.toggle(e, true));
|
||||
}
|
||||
}
|
||||
validate(): boolean
|
||||
static override validate(builder: CharacterBuilder): boolean
|
||||
{
|
||||
return this._builder.character.people !== undefined;
|
||||
}
|
||||
get dom()
|
||||
{
|
||||
return this._content;
|
||||
return builder.character.people !== undefined;
|
||||
}
|
||||
}
|
||||
class LevelPicker implements BuilderTab
|
||||
class LevelPicker extends BuilderTab
|
||||
{
|
||||
private _builder: CharacterBuilder;
|
||||
private _content: Array<Node | string>;
|
||||
|
||||
private _levelInput: HTMLInputElement;
|
||||
private _pointsInput: HTMLInputElement;
|
||||
private _healthText: Text;
|
||||
private _manaText: Text;
|
||||
private _options: HTMLDivElement[][];
|
||||
|
||||
static override name = 'Niveaux';
|
||||
static override description = 'Déterminez la progression de votre personnage en choisissant une option par niveau disponible.';
|
||||
|
||||
constructor(builder: CharacterBuilder)
|
||||
{
|
||||
this._builder = builder;
|
||||
super(builder);
|
||||
|
||||
this._levelInput = numberpicker({ defaultValue: this._builder.character.level, min: 1, max: 20, input: (value) => {
|
||||
this._builder.character.level = value;
|
||||
|
|
@ -814,7 +795,7 @@ class LevelPicker implements BuilderTab
|
|||
button(text('Suivant'), () => this._builder.display(2), 'h-[35px] px-[15px]'),
|
||||
]), div('flex flex-col flex-1 gap-4 mx-8 my-4', this._options.flatMap(e => [...e]))];
|
||||
}
|
||||
update()
|
||||
override update()
|
||||
{
|
||||
const values = this._builder.values;
|
||||
|
||||
|
|
@ -839,20 +820,13 @@ class LevelPicker implements BuilderTab
|
|||
});
|
||||
}); */
|
||||
}
|
||||
validate(): boolean
|
||||
static override validate(builder: CharacterBuilder): boolean
|
||||
{
|
||||
return this._builder.character.level - Object.keys(this._builder.character.leveling).length >= 0;
|
||||
}
|
||||
get dom()
|
||||
{
|
||||
return this._content;
|
||||
return builder.character.level - Object.keys(builder.character.leveling).length >= 0;
|
||||
}
|
||||
}
|
||||
class TrainingPicker implements BuilderTab
|
||||
class TrainingPicker extends BuilderTab
|
||||
{
|
||||
private _builder: CharacterBuilder;
|
||||
private _content: Array<Node | string>;
|
||||
|
||||
private _pointsInput: HTMLInputElement;
|
||||
private _healthText: Text;
|
||||
private _manaText: Text;
|
||||
|
|
@ -862,15 +836,18 @@ class TrainingPicker implements BuilderTab
|
|||
private _statIndicator: HTMLSpanElement;
|
||||
private _statContainer: HTMLDivElement;
|
||||
|
||||
static override name = 'Entrainement';
|
||||
static override description = 'Spécialisez votre personnage en attribuant vos points d\'entrainement parmi les 7 branches disponibles.\nChaque paliers de 3 points augmentent votre modifieur.';
|
||||
|
||||
constructor(builder: CharacterBuilder)
|
||||
{
|
||||
super(builder);
|
||||
const statRenderBlock = (stat: MainStat) => {
|
||||
return Object.entries(config.training[stat]).map(
|
||||
(level) => [ div("w-full flex h-px", [div("border-t border-dashed border-light-50 dark:border-dark-50 w-full"), dom('span', { class: "relative" }, [ text(level[0]) ])]),
|
||||
div("flex flex-row gap-4 justify-center", level[1].map((option, j) => new PickableFeature(option, { state: level[0] == '0' || this._builder.character.training[stat as MainStat][level[0] as any as TrainingLevel] === j, choices: this._builder.character.choices }).dom))
|
||||
])
|
||||
}
|
||||
this._builder = builder;
|
||||
|
||||
this._pointsInput = dom("input", { class: `w-14 mx-4 text-light-70 dark:text-dark-70 tabular-nums bg-light-10 dark:bg-dark-10 appearance-none outline-none ps-3 pe-1 py-1 focus:shadow-raw transition-[box-shadow] border bg-light-20 bg-dark-20 border-light-20 dark:border-dark-20`, attributes: { type: "number", disabled: true }});
|
||||
this._healthText = text("0"), this._manaText = text("0");
|
||||
|
|
@ -912,7 +889,7 @@ class TrainingPicker implements BuilderTab
|
|||
|
||||
this._statContainer.style.left = `-${tab * 100}%`;
|
||||
}
|
||||
update()
|
||||
override update()
|
||||
{
|
||||
const values = this._builder.values;
|
||||
const training = Object.values(this._builder.character.training).reduce((p, v) => p + Object.values(v).filter(e => e !== undefined).length, 0);
|
||||
|
|
@ -921,31 +898,28 @@ class TrainingPicker implements BuilderTab
|
|||
this._healthText.textContent = values.health?.toString() ?? '0';
|
||||
this._manaText.textContent = values.mana?.toString() ?? '0';
|
||||
}
|
||||
validate(): boolean
|
||||
static override validate(builder: CharacterBuilder): boolean
|
||||
{
|
||||
const values = this._builder.values;
|
||||
const training = Object.values(this._builder.character.training).reduce((p, v) => p + Object.values(v).filter(e => e !== undefined).length, 0);
|
||||
const values = builder.values;
|
||||
const training = Object.values(builder.character.training).reduce((p, v) => p + Object.values(v).filter(e => e !== undefined).length, 0);
|
||||
|
||||
return (values.training ?? 0) - training >= 0;
|
||||
}
|
||||
get dom()
|
||||
{
|
||||
return this._content;
|
||||
}
|
||||
}
|
||||
class AbilityPicker implements BuilderTab
|
||||
class AbilityPicker extends BuilderTab
|
||||
{
|
||||
private _builder: CharacterBuilder;
|
||||
private _content: Array<Node | string>;
|
||||
|
||||
private _pointsInput: HTMLInputElement;
|
||||
private _options: HTMLDivElement[];
|
||||
|
||||
private _tooltips: Text[] = [];
|
||||
private _maxs: HTMLElement[] = [];
|
||||
|
||||
static override name = 'Compétences';
|
||||
static override description = 'Diversifiez vos possibilités en affectant vos points dans les différentes compétences disponibles.';
|
||||
|
||||
constructor(builder: CharacterBuilder)
|
||||
{
|
||||
super(builder);
|
||||
const numberInput = (value?: number, update?: (value: number) => number | undefined) => {
|
||||
const input = dom("input", { class: `w-14 mx-4 caret-light-50 dark:caret-dark-50 text-light-100 dark:text-dark-100 placeholder:text-light-50 dark:placeholder:text-dark-50 bg-light-20 dark:bg-dark-20 appearance-none outline-none px-3 py-1 focus:shadow-raw transition-[box-shadow] focus:shadow-light-40 dark:focus:shadow-dark-40 border border-light-35 dark:border-dark-35 hover:border-light-50 dark:hover:border-dark-50 data-[disabled]:bg-light-20 dark:data-[disabled]:bg-dark-20 data-[disabled]:border-light-20 dark:data-[disabled]:border-dark-20`, listeners: {
|
||||
input: (e: Event) => {
|
||||
|
|
@ -980,7 +954,6 @@ class AbilityPicker implements BuilderTab
|
|||
arr.push(value);
|
||||
return value;
|
||||
}
|
||||
this._builder = builder;
|
||||
|
||||
this._pointsInput = dom("input", { class: `w-14 mx-4 text-light-70 dark:text-dark-70 tabular-nums bg-light-10 dark:bg-dark-10 appearance-none outline-none ps-3 pe-1 py-1 focus:shadow-raw transition-[box-shadow] border bg-light-20 bg-dark-20 border-light-20 dark:border-dark-20`, attributes: { type: "number", disabled: true }});
|
||||
|
||||
|
|
@ -1017,7 +990,7 @@ class AbilityPicker implements BuilderTab
|
|||
button(text('Suivant'), () => this._builder.display(4), 'h-[35px] px-[15px]'),
|
||||
]), div('flex flex-row flex-wrap justify-center items-center flex-1 gap-12 mx-8 my-4 px-48', this._options)];
|
||||
}
|
||||
update()
|
||||
override update()
|
||||
{
|
||||
const values = this._builder.values;
|
||||
const abilities = Object.values(this._builder.character.abilities).reduce((p, v) => p + v, 0);
|
||||
|
|
@ -1034,23 +1007,16 @@ class AbilityPicker implements BuilderTab
|
|||
return this._builder.character.abilities[e];
|
||||
})
|
||||
}
|
||||
validate(): boolean
|
||||
static override validate(builder: CharacterBuilder): boolean
|
||||
{
|
||||
const values = this._builder.values;
|
||||
const abilities = Object.values(this._builder.character.abilities).reduce((p, v) => p + v, 0);
|
||||
const values = builder.values;
|
||||
const abilities = Object.values(builder.character.abilities).reduce((p, v) => p + v, 0);
|
||||
|
||||
return (values.ability ?? 0) - abilities >= 0;
|
||||
}
|
||||
get dom()
|
||||
{
|
||||
return this._content;
|
||||
}
|
||||
}
|
||||
class AspectPicker implements BuilderTab
|
||||
class AspectPicker extends BuilderTab
|
||||
{
|
||||
private _builder: CharacterBuilder;
|
||||
private _content: Array<Node | string>;
|
||||
|
||||
private _physicInput: HTMLInputElement;
|
||||
private _mentalInput: HTMLInputElement;
|
||||
private _personalityInput: HTMLInputElement;
|
||||
|
|
@ -1059,9 +1025,12 @@ class AspectPicker implements BuilderTab
|
|||
|
||||
private _options: HTMLDivElement[];
|
||||
|
||||
static override name = 'Aspect';
|
||||
static override description = 'Déterminez l\'Aspect qui vous corresponds et benéficiez de puissants bonus.';
|
||||
|
||||
constructor(builder: CharacterBuilder)
|
||||
{
|
||||
this._builder = builder;
|
||||
super(builder);
|
||||
|
||||
this._physicInput = dom("input", { class: `w-14 mx-4 text-light-70 dark:text-dark-70 tabular-nums bg-light-10 dark:bg-dark-10 appearance-none outline-none ps-3 pe-1 py-1 focus:shadow-raw transition-[box-shadow] border bg-light-20 bg-dark-20 border-light-20 dark:border-dark-20`, attributes: { type: "number", disabled: true }});
|
||||
this._mentalInput = dom("input", { class: `w-14 mx-4 text-light-70 dark:text-dark-70 tabular-nums bg-light-10 dark:bg-dark-10 appearance-none outline-none ps-3 pe-1 py-1 focus:shadow-raw transition-[box-shadow] border bg-light-20 bg-dark-20 border-light-20 dark:border-dark-20`, attributes: { type: "number", disabled: true }});
|
||||
|
|
@ -1119,7 +1088,7 @@ class AspectPicker implements BuilderTab
|
|||
button(text('Enregistrer'), () => this._builder.save(), 'h-[35px] px-[15px]'),
|
||||
]), div('flex flex-row flex-wrap justify-center items-center flex-1 gap-8 mx-8 my-4 px-8', this._options)];
|
||||
}
|
||||
update()
|
||||
override update()
|
||||
{
|
||||
const physic = Object.values(this._builder.character.training['strength']).length + Object.values(this._builder.character.training['dexterity']).length + Object.values(this._builder.character.training['constitution']).length;
|
||||
const mental = Object.values(this._builder.character.training['intelligence']).length + Object.values(this._builder.character.training['curiosity']).length;
|
||||
|
|
@ -1148,16 +1117,16 @@ class AspectPicker implements BuilderTab
|
|||
return true;
|
||||
}));
|
||||
}
|
||||
validate(): boolean
|
||||
static override validate(builder: CharacterBuilder): boolean
|
||||
{
|
||||
const physic = Object.values(this._builder.character.training['strength']).length + Object.values(this._builder.character.training['dexterity']).length + Object.values(this._builder.character.training['constitution']).length;
|
||||
const mental = Object.values(this._builder.character.training['intelligence']).length + Object.values(this._builder.character.training['curiosity']).length;
|
||||
const personality = Object.values(this._builder.character.training['charisma']).length + Object.values(this._builder.character.training['psyche']).length;
|
||||
const physic = Object.values(builder.character.training['strength']).length + Object.values(builder.character.training['dexterity']).length + Object.values(builder.character.training['constitution']).length;
|
||||
const mental = Object.values(builder.character.training['intelligence']).length + Object.values(builder.character.training['curiosity']).length;
|
||||
const personality = Object.values(builder.character.training['charisma']).length + Object.values(builder.character.training['psyche']).length;
|
||||
|
||||
if(this._builder.character.aspect === undefined)
|
||||
if(builder.character.aspect === undefined)
|
||||
return false;
|
||||
|
||||
const aspect = config.aspects[this._builder.character.aspect]!
|
||||
const aspect = config.aspects[builder.character.aspect]!
|
||||
|
||||
if(physic > aspect.physic.max || physic < aspect.physic.min)
|
||||
return false;
|
||||
|
|
@ -1168,8 +1137,4 @@ class AspectPicker implements BuilderTab
|
|||
|
||||
return true;
|
||||
}
|
||||
get dom()
|
||||
{
|
||||
return this._content;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue