From f755d7f9f52becea42cea267f38a49b339f07d09 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Tue, 17 Jun 2025 21:44:21 +0200 Subject: [PATCH] Added a character setup dialog --- daggerheart.mjs | 13 +- lang/en.json | 44 ++- module/applications/characterCreation.mjs | 306 ++++++++++++++++++ .../sheets/activeEffectConfig.mjs | 2 +- module/applications/sheets/character.mjs | 8 +- module/config/domainConfig.mjs | 18 +- module/data/actor/character.mjs | 12 +- module/data/item/subclass.mjs | 2 +- module/dialogs/selectDialog.mjs | 97 ------ module/placeables/measuredTemplate.mjs | 2 +- styles/characterCreation.less | 277 ++++++++++++++++ styles/daggerheart.css | 236 ++++++++++++++ styles/daggerheart.less | 1 + templates/components/card-preview.hbs | 5 +- templates/sheets/character/character.hbs | 1 + templates/sheets/items/armor/settings.hbs | 1 - templates/views/characterCreation/footer.hbs | 29 ++ templates/views/characterCreation/tabs.hbs | 13 + .../characterCreation/tabs/equipment.hbs | 9 + .../views/characterCreation/tabs/setup.hbs | 126 ++++++++ .../views/characterCreation/tabs/story.hbs | 9 + 21 files changed, 1077 insertions(+), 134 deletions(-) create mode 100644 module/applications/characterCreation.mjs delete mode 100644 module/dialogs/selectDialog.mjs create mode 100644 styles/characterCreation.less create mode 100644 templates/views/characterCreation/footer.hbs create mode 100644 templates/views/characterCreation/tabs.hbs create mode 100644 templates/views/characterCreation/tabs/equipment.hbs create mode 100644 templates/views/characterCreation/tabs/setup.hbs create mode 100644 templates/views/characterCreation/tabs/story.hbs diff --git a/daggerheart.mjs b/daggerheart.mjs index c5c685cd..b4caca2a 100644 --- a/daggerheart.mjs +++ b/daggerheart.mjs @@ -77,14 +77,19 @@ Hooks.once('init', () => { Actors.registerSheet(SYSTEM.id, applications.DhpEnvironment, { types: ['environment'], makeDefault: true }); CONFIG.ActiveEffect.documentClass = documents.DhActiveEffect; - DocumentSheetConfig.unregisterSheet( + foundry.applications.apps.DocumentSheetConfig.unregisterSheet( CONFIG.ActiveEffect.documentClass, 'core', foundry.applications.sheets.ActiveEffectConfig ); - DocumentSheetConfig.registerSheet(CONFIG.ActiveEffect.documentClass, SYSTEM.id, applications.DhActiveEffectConfig, { - makeDefault: true - }); + foundry.applications.apps.DocumentSheetConfig.registerSheet( + CONFIG.ActiveEffect.documentClass, + SYSTEM.id, + applications.DhActiveEffectConfig, + { + makeDefault: true + } + ); CONFIG.Combat.dataModels = { base: models.DhCombat diff --git a/lang/en.json b/lang/en.json index 1417e905..22f453a3 100755 --- a/lang/en.json +++ b/lang/en.json @@ -367,39 +367,39 @@ } }, "Domains": { - "Arcana": { + "arcana": { "label": "Arcana", "Description": "This is the domain of the innate or instinctual use of magic. Those who walk this path tap into the raw, enigmatic forces of the realms to manipulate both the elements and their own energy. Arcana offers wielders a volatile power, but it is incredibly potent when correctly channeled." }, - "Blade": { + "blade": { "label": "Blade", "Description": "This is the domain of those who dedicate their lives to the mastery of weapons. Whether by blade, bow, or perhaps a more specialized arm, those who follow this path have the skill to cut short the lives of others. Blade requires study and dedication from its followers, in exchange for inexorable power over death." }, - "Bone": { + "bone": { "label": "Bone", "Description": "This is the domain of mastery of swiftness and tactical mastery. Practitioners of this domain have an uncanny control over their own physical abilities, and an eye for predicting the behaviors of others in combat. Bone grants its adherents unparalleled understanding of bodies and their movements in exchange for diligent training." }, - "Codex": { + "codex": { "label": "Codex", "Description": "This is the domain of intensive magical study. Those who seek magical knowledge turn to the recipes of power recorded in books, on scrolls, etched into walls, or tattooed on bodies. Codex offers a commanding and versatile understanding of magic to those devotees who are willing to seek beyond the common knowledge." }, - "Grace": { + "grace": { "label": "Grace", "Description": "This is the domain of charisma. Through rapturous storytelling, clever charm, or a shroud of lies, those who channel this power define the realities of their adversaries, bending perception to their will. Grace offers its wielders raw magnetism and mastery over language." }, - "Midnight": { + "midnight": { "label": "Midnight", "Description": "This is the domain of shadows and secrecy. Whether by clever tricks, or cloak of night those who channel these forces are practiced in that art of obscurity and there is nothing hidden they cannot reach. Midnight offers practitioners the incredible power to control and create enigmas." }, - "Sage": { + "sage": { "label": "Sage", "Description": "This is the domain of the natural world. Those who walk this path tap into the unfettered power of the earth and its creatures to unleash raw magic. Sage grants its adherents the vitality of a blooming flower and ferocity of a hungry predator." }, - "Splendor": { + "splendor": { "label": "Splendor", "Description": "This is the domain of life. Through this magic, followers gain the ability to heal, though such power also grants the wielder some control over death. Splendor offers its disciples the magnificent ability to both give and end life." }, - "Valor": { + "valor": { "label": "Valor", "Description": "This is the domain of protection. Whether through attack or defense, those who choose this discipline channel formidable strength to protect their allies in battle. Valor offers great power to those who raise their shield in defense of others." } @@ -417,6 +417,32 @@ "requestingSpotlight": "Requesting The Spotlight", "combatStarted": "Active" }, + "CharacterCreation": { + "Title": "{actor} - Character Setup", + "TraitIncreases": "Trait Increases", + "SuggestedTraits": "Suggested Traits", + "InitialExperiences": "Initial Experiences", + "Heritage": "Heritage", + "SelectAncestry": "Select Ancestry", + "SelectCommunity": "Select Community", + "SelectClass": "Select Class", + "SelectSubclass": "Select Subclass", + "NewExperience": "New Experience..", + "FinishCreation": "Finish Character Setup", + "Tabs": { + "Optional": "Optional", + "Setup": "Setup", + "Equipment": "Equipment", + "Story": "Story" + }, + "Notifications": { + "SubclassNotInClass": "This subclass does not belong to your selected class.", + "MissingClass": "You don't have a class selected yet.", + "WrongDomain": "The card isn't from one of your class domains.", + "CardTooHighLevel": "The card is too high level!", + "DuplicateCard": "You cannot select the same card more than once." + } + }, "LevelUp": { "Options": { "trait": "Gain a +1 bonus to two unmarked character traits and mark them.", diff --git a/module/applications/characterCreation.mjs b/module/applications/characterCreation.mjs new file mode 100644 index 00000000..372580bf --- /dev/null +++ b/module/applications/characterCreation.mjs @@ -0,0 +1,306 @@ +import { abilities } from '../config/actorConfig.mjs'; + +const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; + +export default class DhCharacterCreation extends HandlebarsApplicationMixin(ApplicationV2) { + constructor(character) { + super({}); + + this.character = character; + + this.setup = { + traits: this.character.system.traits, + ancestry: this.character.system.ancestry ?? {}, + community: this.character.system.community ?? {}, + class: this.character.system.class?.value ?? {}, + subclass: this.character.system.class?.subclass ?? {}, + experiences: { + [foundry.utils.randomID()]: { description: '', value: 2 }, + [foundry.utils.randomID()]: { description: '', value: 2 } + }, + domainCards: { + [foundry.utils.randomID()]: {}, + [foundry.utils.randomID()]: {} + } + }; + this.visibility = 5; + + this._dragDrop = this._createDragDropHandlers(); + } + + get title() { + return game.i18n.format('DAGGERHEART.CharacterCreation.Title', { actor: this.character.name }); + } + + static DEFAULT_OPTIONS = { + tag: 'form', + classes: ['daggerheart', 'dialog', 'dh-style', 'character-creation'], + position: { width: 800, height: 'auto' }, + actions: { + viewCompendium: this.viewCompendium, + useSuggestedTraits: this.useSuggestedTraits, + finish: this.finish + }, + form: { + handler: this.updateForm, + submitOnChange: true, + closeOnSubmit: false + }, + dragDrop: [ + { dragSelector: null, dropSelector: '.ancestry-card' }, + { dragSelector: null, dropSelector: '.community-card' }, + { dragSelector: null, dropSelector: '.class-card' }, + { dragSelector: null, dropSelector: '.subclass-card' }, + { dragSelector: null, dropSelector: '.domain-card' } + ] + }; + + static PARTS = { + tabs: { template: 'systems/daggerheart/templates/views/characterCreation/tabs.hbs' }, + setup: { template: 'systems/daggerheart/templates/views/characterCreation/tabs/setup.hbs' }, + equipment: { template: 'systems/daggerheart/templates/views/characterCreation/tabs/equipment.hbs' }, + story: { template: 'systems/daggerheart/templates/views/characterCreation/tabs/story.hbs' }, + footer: { template: 'systems/daggerheart/templates/views/characterCreation/footer.hbs' } + }; + + static TABS = { + setup: { + active: true, + cssClass: '', + group: 'primary', + id: 'setup', + label: 'DAGGERHEART.CharacterCreation.Tabs.Setup' + }, + equipment: { + active: false, + cssClass: '', + group: 'primary', + id: 'equipment', + label: 'DAGGERHEART.CharacterCreation.Tabs.Equipment', + optional: true + }, + story: { + active: false, + cssClass: '', + group: 'primary', + id: 'story', + label: 'DAGGERHEART.CharacterCreation.Tabs.Story', + optional: true + } + }; + + _getTabs(tabs) { + for (const v of Object.values(tabs)) { + v.active = this.tabGroups[v.group] ? this.tabGroups[v.group] === v.id : v.active; + v.cssClass = v.active ? 'active' : ''; + + switch (v.id) { + case 'setup': + const classFinished = this.setup.class.uuid && this.setup.subclass.uuid; + const heritageFinished = this.setup.ancestry.uuid && this.setup.community.uuid; + const traitsFinished = Object.values(this.setup.traits).every(x => x.value !== null); + const experiencesFinished = Object.values(this.setup.experiences).every(x => x.description); + const domainCardsFinished = Object.values(this.setup.domainCards).every(x => x.uuid); + v.finished = + classFinished && + heritageFinished && + traitsFinished && + experiencesFinished && + domainCardsFinished; + break; + } + } + + tabs.equipment.cssClass = tabs.setup.finished ? tabs.equipment.cssClass : 'disabled'; + tabs.story.cssClass = tabs.setup.finished ? tabs.story.cssClass : 'disabled'; + + return tabs; + } + + _attachPartListeners(partId, htmlElement, options) { + super._attachPartListeners(partId, htmlElement, options); + + this._dragDrop.forEach(d => d.bind(htmlElement)); + } + + async _prepareContext(_options) { + const context = await super._prepareContext(_options); + context.tabs = this._getTabs(this.constructor.TABS); + + const availableTraitModifiers = game.settings + .get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Homebrew) + .traitArray.map(trait => ({ key: trait, name: trait })); + for (let trait of Object.values(this.setup.traits).filter(x => x.value !== null)) { + const index = availableTraitModifiers.findIndex(x => x.key === trait.value); + if (index !== -1) { + availableTraitModifiers.splice(index, 1); + } + } + + context.suggestedTraits = this.setup.class.system + ? Object.keys(this.setup.class.system.characterGuide.suggestedTraits).map(traitKey => { + const trait = this.setup.class.system.characterGuide.suggestedTraits[traitKey]; + return `${game.i18n.localize(`DAGGERHEART.Abilities.${traitKey}.short`)} ${trait > 0 ? `+${trait}` : trait}`; + }) + : []; + context.traits = { + values: Object.keys(this.setup.traits).map(traitKey => { + const trait = this.setup.traits[traitKey]; + const options = [...availableTraitModifiers]; + if (trait.value !== null && !options.some(x => x.key === trait.value)) + options.push({ key: trait.value, name: trait.value }); + + return { + ...trait, + key: traitKey, + name: game.i18n.localize(abilities[traitKey].label), + options: options + }; + }) + }; + context.traits.nrTotal = Object.keys(context.traits.values).length; + context.traits.nrSelected = Object.values(context.traits.values).reduce( + (acc, trait) => acc + (trait.value !== null ? 1 : 0), + 0 + ); + + context.experience = { + values: this.setup.experiences, + nrTotal: Object.keys(this.setup.experiences).length, + nrSelected: Object.values(this.setup.experiences).reduce((acc, exp) => acc + (exp.description ? 1 : 0), 0) + }; + + context.ancestry = { ...this.setup.ancestry, compendium: 'ancestries' }; + context.community = { ...this.setup.community, compendium: 'communities' }; + context.class = { ...this.setup.class, compendium: 'classes' }; + context.subclass = { ...this.setup.subclass, compendium: 'subclasses' }; + context.domainCards = Object.keys(this.setup.domainCards).reduce((acc, x) => { + acc[x] = { ...this.setup.domainCards[x], compendium: 'domains' }; + return acc; + }, {}); + + context.visibility = this.visibility; + + return context; + } + + static async updateForm(event, _, formData) { + this.setup = foundry.utils.mergeObject(this.setup, formData.object); + + this.visibility = this.getUpdateVisibility(); + this.render(); + } + + getUpdateVisibility() { + switch (this.visibility) { + case 5: + return 5; + case 4: + return Object.values(this.setup.experiences).every(x => x.description) ? 5 : 4; + case 3: + return Object.values(this.setup.traits).every(x => x.value !== null) ? 4 : 3; + case 2: + return this.setup.ancestry.uuid && this.setup.community.uuid ? 3 : 2; + case 1: + return this.setup.class.uuid && this.setup.subclass.uuid ? 2 : 1; + } + } + + _createDragDropHandlers() { + return this.options.dragDrop.map(d => { + d.callbacks = { + drop: this._onDrop.bind(this) + }; + return new foundry.applications.ux.DragDrop.implementation(d); + }); + } + + static async viewCompendium(_, target) { + (await game.packs.get(`daggerheart.${target.dataset.compendium}`))?.render(true); + } + + static useSuggestedTraits() { + this.setup.traits = Object.keys(this.setup.traits).reduce((acc, traitKey) => { + acc[traitKey] = { + ...this.setup.traits[traitKey], + value: this.setup.class.system.characterGuide.suggestedTraits[traitKey] + }; + return acc; + }, {}); + this.render(); + } + + static async finish() { + const embeddedAncestries = await this.character.createEmbeddedDocuments('Item', [this.setup.ancestry]); + const embeddedCommunities = await this.character.createEmbeddedDocuments('Item', [this.setup.community]); + await this.character.createEmbeddedDocuments('Item', [this.setup.class]); + await this.character.createEmbeddedDocuments('Item', [this.setup.subclass]); + await this.character.createEmbeddedDocuments('Item', Object.values(this.setup.domainCards)); + + await this.character.update({ + system: { + traits: this.setup.traits, + experiences: this.setup.experiences, + ancestry: embeddedAncestries[0].uuid, + community: embeddedCommunities[0].uuid + } + }); + + this.close(); + } + + async _onDrop(event) { + const data = TextEditor.getDragEventData(event); + const item = await foundry.utils.fromUuid(data.uuid); + if (item.type === 'ancestry' && event.target.closest('.ancestry-card')) { + this.setup.ancestry = { ...item, uuid: item.uuid }; + } else if (item.type === 'community' && event.target.closest('.community-card')) { + this.setup.community = { ...item, uuid: item.uuid }; + } else if (item.type === 'class' && event.target.closest('.class-card')) { + this.setup.class = { ...item, uuid: item.uuid }; + this.setup.subclass = {}; + this.setup.domainCards = { + [foundry.utils.randomID()]: {}, + [foundry.utils.randomID()]: {} + }; + } else if (item.type === 'subclass' && event.target.closest('.subclass-card')) { + if (this.setup.class.system.subclasses.every(subclass => subclass.uuid !== item.uuid)) { + ui.notifications.error( + game.i18n.localize('DAGGERHEART.CharacterCreation.Notifications.SubclassNotInClass') + ); + return; + } + + this.setup.subclass = { ...item, uuid: item.uuid }; + } else if (item.type === 'domainCard' && event.target.closest('.domain-card')) { + if (!this.setup.class.uuid) { + ui.notifications.error(game.i18n.localize('DAGGERHEART.CharacterCreation.Notifications.MissingClass')); + return; + } + + if (!this.setup.class.system.domains.includes(item.system.domain)) { + ui.notifications.error(game.i18n.localize('DAGGERHEART.CharacterCreation.Notifications.WrongDomain')); + return; + } + + if (item.system.level > 1) { + ui.notifications.error( + game.i18n.localize('DAGGERHEART.CharacterCreation.Notifications.CardTooHighLevel') + ); + return; + } + + if (Object.values(this.setup.domainCards).some(card => card.uuid === item.uuid)) { + ui.notifications.error(game.i18n.localize('DAGGERHEART.CharacterCreation.Notifications.DuplicateCard')); + return; + } + + this.setup.domainCards[event.target.closest('.domain-card').dataset.card] = { ...item, uuid: item.uuid }; + } else { + return; + } + + this.visibility = this.getUpdateVisibility(); + this.render(); + } +} diff --git a/module/applications/sheets/activeEffectConfig.mjs b/module/applications/sheets/activeEffectConfig.mjs index 585086a1..6a629583 100644 --- a/module/applications/sheets/activeEffectConfig.mjs +++ b/module/applications/sheets/activeEffectConfig.mjs @@ -1,4 +1,4 @@ -export default class DhActiveEffectConfig extends ActiveEffectConfig { +export default class DhActiveEffectConfig extends foundry.applications.sheets.ActiveEffectConfig { static DEFAULT_OPTIONS = { classes: ['daggerheart', 'sheet', 'dh-style'] }; diff --git a/module/applications/sheets/character.mjs b/module/applications/sheets/character.mjs index fe0beec9..74e001b2 100644 --- a/module/applications/sheets/character.mjs +++ b/module/applications/sheets/character.mjs @@ -6,6 +6,7 @@ import DaggerheartSheet from './daggerheart-sheet.mjs'; import { abilities } from '../../config/actorConfig.mjs'; import DhlevelUp from '../levelup.mjs'; import DHDualityRoll from '../../data/chat-message/dualityRoll.mjs'; +import DhCharacterCreation from '../characterCreation.mjs'; const { ActorSheetV2 } = foundry.applications.sheets; const { TextEditor } = foundry.applications.ux; @@ -47,7 +48,8 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { useAdvancementCard: this.useAdvancementCard, useAdvancementAbility: this.useAdvancementAbility, toggleEquipItem: this.toggleEquipItem, - levelup: this.openLevelUp + levelup: this.openLevelUp, + temp: this.temp }, window: { minimizable: false, @@ -383,6 +385,10 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { new DhlevelUp(this.document).render(true); } + static temp() { + new DhCharacterCreation(this.document).render(true); + } + static async useDomainCard(_, button) { const card = this.document.items.find(x => x.uuid === button.dataset.key); diff --git a/module/config/domainConfig.mjs b/module/config/domainConfig.mjs index a32091c2..3ecb0bd1 100644 --- a/module/config/domainConfig.mjs +++ b/module/config/domainConfig.mjs @@ -1,56 +1,56 @@ export const domains = { arcana: { id: 'arcana', - label: 'DAGGERHEART.Domains.Arcana.label', + label: 'DAGGERHEART.Domains.arcana.label', src: 'icons/magic/symbols/circled-gem-pink.webp', description: 'DAGGERHEART.Domains.Arcana' }, blade: { id: 'blade', - label: 'DAGGERHEART.Domains.Blade.label', + label: 'DAGGERHEART.Domains.blade.label', src: 'icons/weapons/swords/sword-broad-crystal-paired.webp', description: 'DAGGERHEART.Domains.Blade' }, bone: { id: 'bone', - label: 'DAGGERHEART.Domains.Bone.label', + label: 'DAGGERHEART.Domains.bone.label', src: 'icons/skills/wounds/bone-broken-marrow-red.webp', description: 'DAGGERHEART.Domains.Bone' }, codex: { id: 'codex', - label: 'DAGGERHEART.Domains.Codex.label', + label: 'DAGGERHEART.Domains.codex.label', src: 'icons/sundries/books/book-embossed-jewel-gold-purple.webp', description: 'DAGGERHEART.Domains.Codex' }, grace: { id: 'grace', - label: 'DAGGERHEART.Domains.Grace.label', + label: 'DAGGERHEART.Domains.grace.label', src: 'icons/skills/movement/feet-winged-boots-glowing-yellow.webp', description: 'DAGGERHEART.Domains.Grace' }, midnight: { id: 'midnight', - label: 'DAGGERHEART.Domains.Midnight.label', + label: 'DAGGERHEART.Domains.midnight.label', src: 'icons/environment/settlement/watchtower-castle-night.webp', background: 'systems/daggerheart/assets/backgrounds/MidnightBackground.webp', description: 'DAGGERHEART.Domains.Midnight' }, sage: { id: 'sage', - label: 'DAGGERHEART.Domains.Sage.label', + label: 'DAGGERHEART.Domains.sage.label', src: 'icons/sundries/misc/pipe-wooden-straight-brown.webp', description: 'DAGGERHEART.Domains.Sage' }, splendor: { id: 'splendor', - label: 'DAGGERHEART.Domains.Splendor.label', + label: 'DAGGERHEART.Domains.splendor.label', src: 'icons/magic/control/control-influence-crown-gold.webp', description: 'DAGGERHEART.Domains.Splendor' }, valor: { id: 'valor', - label: 'DAGGERHEART.Domains.Valor.label', + label: 'DAGGERHEART.Domains.valor.label', src: 'icons/magic/control/control-influence-rally-purple.webp', description: 'DAGGERHEART.Domains.Valor' } diff --git a/module/data/actor/character.mjs b/module/data/actor/character.mjs index 1ebe217c..8219f89e 100644 --- a/module/data/actor/character.mjs +++ b/module/data/actor/character.mjs @@ -5,7 +5,7 @@ import BaseDataActor from './base.mjs'; const attributeField = () => new foundry.data.fields.SchemaField({ - value: new foundry.data.fields.NumberField({ initial: 0, integer: true }), + value: new foundry.data.fields.NumberField({ initial: null, integer: true }), bonus: new foundry.data.fields.NumberField({ initial: 0, integer: true }), tierMarked: new foundry.data.fields.BooleanField({ initial: false }) }); @@ -54,13 +54,7 @@ export default class DhCharacter extends BaseDataActor { description: new fields.StringField({}), value: new fields.NumberField({ integer: true, initial: 0 }), bonus: new fields.NumberField({ integer: true, initial: 0 }) - }), - { - initial: { - [foundry.utils.randomID()]: { description: '', value: 2 }, - [foundry.utils.randomID()]: { description: '', value: 2 } - } - } + }) ), gold: new fields.SchemaField({ coins: new fields.NumberField({ initial: 0, integer: true }), @@ -235,7 +229,7 @@ export default class DhCharacter extends BaseDataActor { for (var traitKey in this.traits) { var trait = this.traits[traitKey]; - trait.total = trait.value + trait.bonus; + trait.total = (trait.value ?? 0) + trait.bonus; } for (var experienceKey in this.experiences) { diff --git a/module/data/item/subclass.mjs b/module/data/item/subclass.mjs index 1e236ff4..c20f6f30 100644 --- a/module/data/item/subclass.mjs +++ b/module/data/item/subclass.mjs @@ -64,7 +64,7 @@ export default class DHSubclass extends BaseDataItem { } else if (subclassData) { ui.notifications.error(game.i18n.localize('DAGGERHEART.Item.Errors.SubclassAlreadySelected')); return false; - } else if (classData.system.subclasses.every(x => x.uuid !== `Item.${data._id}`)) { + } else if (classData.system.subclasses.every(x => x.uuid !== data.uuid ?? `Item.${data._id}`)) { ui.notifications.error(game.i18n.localize('DAGGERHEART.Item.Errors.SubclassNotInClass')); return false; } diff --git a/module/dialogs/selectDialog.mjs b/module/dialogs/selectDialog.mjs deleted file mode 100644 index 484979cc..00000000 --- a/module/dialogs/selectDialog.mjs +++ /dev/null @@ -1,97 +0,0 @@ -export default class SelectDialog extends Dialog { - constructor(data, options) { - super(options); - - this.data = { - title: data.title, - buttons: data.buttons, - content: foundry.applications.handlebars.renderTemplate( - 'systems/daggerheart/templates/dialog/item-select.hbs', - { - items: data.choices - } - ) - }; - - this.actor = data.actor; - this.actionCostMax = data.actionCostMax; - this.nrChoices = data.nrChoices; - this.validate = data.validate; - } - - async getData(options = {}) { - let buttons = Object.keys(this.data.buttons).reduce((obj, key) => { - let b = this.data.buttons[key]; - b.cssClass = (this.data.default === key ? [key, 'default', 'bright'] : [key]).join(' '); - if (b.condition !== false) obj[key] = b; - return obj; - }, {}); - - const content = await this.data.content; - - return { - content: content, - buttons: buttons - }; - } - - activateListeners(html) { - super.activateListeners(html); - $(html).find('.item-button').click(this.selectChoice); - } - - selectChoice = async event => { - if (this.validate) { - if (!this.validate(event.currentTarget.dataset.validateProp)) { - return; - } - } - - event.currentTarget.classList.toggle('checked'); - $(event.currentTarget).find('i')[0].classList.toggle('checked'); - - const buttons = $(this.element[0]).find('button.checked'); - if (buttons.length === this.nrChoices) { - $(event.currentTarget).closest('.window-content').find('.confirm')[0].disabled = false; - } else { - $(event.currentTarget).closest('.window-content').find('.confirm')[0].disabled = true; - } - }; - - /** - * - * @param {*} data - * choices, actor, title, cancelMessage, nrChoices, validate - * @returns - */ - static async selectItem(data) { - return this.wait({ - title: data.title ?? 'Selection', - buttons: { - no: { - icon: '', - label: game.i18n.localize('DAGGERHEART.General.Cancel'), - callback: _ => { - if (data.cancelMessage) { - ChatMessage.create({ content: data.cancelMessage }); - } - return []; - } - }, - confirm: { - icon: '', - label: game.i18n.localize('DAGGERHEART.General.OK'), - callback: html => { - const buttons = $(html).find('button.checked'); - return buttons.map(key => Number.parseInt(buttons[key].dataset.index)).toArray(); - }, - disabled: true - } - }, - choices: data.choices, - actor: data.actor, - nrChoices: data.nrChoices ?? 1, - validate: data.validate - }); - } -} diff --git a/module/placeables/measuredTemplate.mjs b/module/placeables/measuredTemplate.mjs index ee21af6d..385cf81e 100644 --- a/module/placeables/measuredTemplate.mjs +++ b/module/placeables/measuredTemplate.mjs @@ -1,4 +1,4 @@ -export default class DhMeasuredTemplate extends MeasuredTemplate { +export default class DhMeasuredTemplate extends foundry.canvas.placeables.MeasuredTemplate { _refreshRulerText() { super._refreshRulerText(); diff --git a/styles/characterCreation.less b/styles/characterCreation.less new file mode 100644 index 00000000..dfdbe467 --- /dev/null +++ b/styles/characterCreation.less @@ -0,0 +1,277 @@ +.daggerheart.dh-style.dialog.character-creation { + .window-content { + gap: 16px; + } + + .tab-navigation { + nav { + flex: 1; + + a { + flex: 1; + text-align: center; + display: flex; + justify-content: center; + position: relative; + + &.disabled { + opacity: 0.4; + } + + .nav-section-text { + position: relative; + display: flex; + align-items: center; + } + + .finish-marker { + position: absolute; + align-self: center; + top: -8px; + padding: 4px; + border: 1px solid; + border-radius: 50%; + height: 16px; + width: 16px; + font-size: 12px; + display: flex; + align-items: center; + justify-content: center; + background-color: var(--color-cool-4); + content: ''; + + &.active { + background-color: var(--color-warm-2); + } + } + + .descriptor { + position: absolute; + bottom: -8px; + font-size: 12px; + border-radius: 8px; + width: 56px; + text-align: center; + line-height: 1; + border: 1px solid light-dark(@dark-blue, @golden); + border-radius: 6px; + color: light-dark(@beige, @dark); + background-image: url(../assets/parchments/dh-parchment-light.png); + } + } + } + } + + .main-selections-container { + display: flex; + flex-direction: column; + gap: 4px; + + .section-container { + border-radius: 8px; + border-color: light-dark(@dark-blue, @golden); + + legend { + margin-left: auto; + margin-right: auto; + font-size: 28px; + font-weight: bold; + padding: 0 8px; + } + + .section-inner-container { + position: relative; + border-radius: 8px; + border-color: light-dark(@dark-blue, @golden); + display: flex; + justify-content: center; + + legend { + font-size: 20px; + } + + .action-button { + position: absolute; + bottom: -8px; + height: 16px; + width: 110px; + min-height: unset; + border: 1px solid light-dark(@dark-blue, @golden); + color: light-dark(@dark, @beige); + background-color: var(--color-warm-3); + + &:hover { + background-color: var(--color-warm-2); + filter: drop-shadow(0 0 3px var(--color-warm-2)); + } + } + } + } + + .traits-container { + text-align: center; + display: flex; + gap: 16px; + + .suggested-traits-container { + display: flex; + flex-wrap: wrap; + width: 176px; + gap: 4px; + margin-bottom: 8px; + + .suggested-trait-container { + width: 56px; + white-space: nowrap; + border: 1px solid light-dark(@dark-blue, @golden); + border-radius: 6px; + color: light-dark(@beige, @dark); + background-image: url('../assets/parchments/dh-parchment-light.png'); + } + } + + .traits-inner-container { + display: flex; + justify-content: space-evenly; + gap: 8px; + + .trait-container { + border: 1px solid light-dark(@dark-blue, @golden); + padding: 0 4px; + } + } + } + + .experiences-inner-container { + display: flex; + justify-content: space-evenly; + text-align: center; + + .experience-container { + position: relative; + display: flex; + align-items: center; + + .experience-description { + border-color: light-dark(@dark-blue, @golden); + padding-right: 24px; + } + + .experience-value { + position: absolute; + right: 0; + width: 22px; + border-left: 1px solid light-dark(@dark-blue, @golden); + height: 100%; + display: flex; + align-items: center; + justify-content: center; + } + } + } + + .selections-container { + display: flex; + justify-content: space-evenly; + height: 210px; + + .selections-inner-container { + width: 140px; + display: flex; + flex-direction: column; + text-align: center; + + .card-preview-container { + border-color: light-dark(@dark-blue, @golden); + } + } + } + + .creation-action-footer { + display: flex; + align-items: center; + gap: 32px; + + .footer-section { + display: flex; + align-items: center; + gap: 32px; + + nav { + flex: 1; + gap: 8px; + border: 0; + + a { + flex: 1; + text-align: center; + display: flex; + justify-content: center; + position: relative; + border: 1px solid light-dark(@dark-blue, @golden); + border-radius: 6px; + + .nav-section-text { + position: relative; + display: flex; + align-items: center; + } + + .finish-marker { + position: absolute; + align-self: center; + top: -10px; + padding: 4px; + border: 1px solid; + border-radius: 50%; + height: 20px; + width: 20px; + font-size: 14px; + display: flex; + align-items: center; + justify-content: center; + background-color: var(--color-cool-4); + content: ''; + + &.finished { + background-color: var(--color-warm-2); + } + } + + .descriptor { + position: absolute; + bottom: -8px; + font-size: 12px; + border-radius: 8px; + width: 56px; + text-align: center; + line-height: 1; + border: 1px solid light-dark(@dark-blue, @golden); + border-radius: 6px; + color: light-dark(@beige, @dark); + background-image: url(../assets/parchments/dh-parchment-light.png); + } + } + } + + button { + flex: 1; + height: 100%; + white-space: nowrap; + } + } + } + } + + .creation-action-footer { + display: flex; + align-items: center; + gap: 32px; + + button { + flex: 1; + height: 100%; + white-space: nowrap; + } + } +} diff --git a/styles/daggerheart.css b/styles/daggerheart.css index fee3a88b..dfc0fd9e 100755 --- a/styles/daggerheart.css +++ b/styles/daggerheart.css @@ -2489,6 +2489,242 @@ div.daggerheart.views.multiclass { .item-button .item-icon.checked { opacity: 1; } +.daggerheart.dh-style.dialog.character-creation .window-content { + gap: 16px; +} +.daggerheart.dh-style.dialog.character-creation .tab-navigation nav { + flex: 1; +} +.daggerheart.dh-style.dialog.character-creation .tab-navigation nav a { + flex: 1; + text-align: center; + display: flex; + justify-content: center; + position: relative; +} +.daggerheart.dh-style.dialog.character-creation .tab-navigation nav a.disabled { + opacity: 0.4; +} +.daggerheart.dh-style.dialog.character-creation .tab-navigation nav a .nav-section-text { + position: relative; + display: flex; + align-items: center; +} +.daggerheart.dh-style.dialog.character-creation .tab-navigation nav a .finish-marker { + position: absolute; + align-self: center; + top: -8px; + padding: 4px; + border: 1px solid; + border-radius: 50%; + height: 16px; + width: 16px; + font-size: 12px; + display: flex; + align-items: center; + justify-content: center; + background-color: var(--color-cool-4); + content: ""; +} +.daggerheart.dh-style.dialog.character-creation .tab-navigation nav a .finish-marker.active { + background-color: var(--color-warm-2); +} +.daggerheart.dh-style.dialog.character-creation .tab-navigation nav a .descriptor { + position: absolute; + bottom: -8px; + font-size: 12px; + border-radius: 8px; + width: 56px; + text-align: center; + line-height: 1; + border: 1px solid light-dark(#18162e, #f3c267); + border-radius: 6px; + color: light-dark(#efe6d8, #222); + background-image: url(../assets/parchments/dh-parchment-light.png); +} +.daggerheart.dh-style.dialog.character-creation .main-selections-container { + display: flex; + flex-direction: column; + gap: 4px; +} +.daggerheart.dh-style.dialog.character-creation .main-selections-container .section-container { + border-radius: 8px; + border-color: light-dark(#18162e, #f3c267); +} +.daggerheart.dh-style.dialog.character-creation .main-selections-container .section-container legend { + margin-left: auto; + margin-right: auto; + font-size: 28px; + font-weight: bold; + padding: 0 8px; +} +.daggerheart.dh-style.dialog.character-creation .main-selections-container .section-container .section-inner-container { + position: relative; + border-radius: 8px; + border-color: light-dark(#18162e, #f3c267); + display: flex; + justify-content: center; +} +.daggerheart.dh-style.dialog.character-creation .main-selections-container .section-container .section-inner-container legend { + font-size: 20px; +} +.daggerheart.dh-style.dialog.character-creation .main-selections-container .section-container .section-inner-container .action-button { + position: absolute; + bottom: -8px; + height: 16px; + width: 110px; + min-height: unset; + border: 1px solid light-dark(#18162e, #f3c267); + color: light-dark(#222, #efe6d8); + background-color: var(--color-warm-3); +} +.daggerheart.dh-style.dialog.character-creation .main-selections-container .section-container .section-inner-container .action-button:hover { + background-color: var(--color-warm-2); + filter: drop-shadow(0 0 3px var(--color-warm-2)); +} +.daggerheart.dh-style.dialog.character-creation .main-selections-container .traits-container { + text-align: center; + display: flex; + gap: 16px; +} +.daggerheart.dh-style.dialog.character-creation .main-selections-container .traits-container .suggested-traits-container { + display: flex; + flex-wrap: wrap; + width: 176px; + gap: 4px; + margin-bottom: 8px; +} +.daggerheart.dh-style.dialog.character-creation .main-selections-container .traits-container .suggested-traits-container .suggested-trait-container { + width: 56px; + white-space: nowrap; + border: 1px solid light-dark(#18162e, #f3c267); + border-radius: 6px; + color: light-dark(#efe6d8, #222); + background-image: url('../assets/parchments/dh-parchment-light.png'); +} +.daggerheart.dh-style.dialog.character-creation .main-selections-container .traits-container .traits-inner-container { + display: flex; + justify-content: space-evenly; + gap: 8px; +} +.daggerheart.dh-style.dialog.character-creation .main-selections-container .traits-container .traits-inner-container .trait-container { + border: 1px solid light-dark(#18162e, #f3c267); + padding: 0 4px; +} +.daggerheart.dh-style.dialog.character-creation .main-selections-container .experiences-inner-container { + display: flex; + justify-content: space-evenly; + text-align: center; +} +.daggerheart.dh-style.dialog.character-creation .main-selections-container .experiences-inner-container .experience-container { + position: relative; + display: flex; + align-items: center; +} +.daggerheart.dh-style.dialog.character-creation .main-selections-container .experiences-inner-container .experience-container .experience-description { + border-color: light-dark(#18162e, #f3c267); + padding-right: 24px; +} +.daggerheart.dh-style.dialog.character-creation .main-selections-container .experiences-inner-container .experience-container .experience-value { + position: absolute; + right: 0; + width: 22px; + border-left: 1px solid light-dark(#18162e, #f3c267); + height: 100%; + display: flex; + align-items: center; + justify-content: center; +} +.daggerheart.dh-style.dialog.character-creation .main-selections-container .selections-container { + display: flex; + justify-content: space-evenly; + height: 210px; +} +.daggerheart.dh-style.dialog.character-creation .main-selections-container .selections-container .selections-inner-container { + width: 140px; + display: flex; + flex-direction: column; + text-align: center; +} +.daggerheart.dh-style.dialog.character-creation .main-selections-container .selections-container .selections-inner-container .card-preview-container { + border-color: light-dark(#18162e, #f3c267); +} +.daggerheart.dh-style.dialog.character-creation .main-selections-container .creation-action-footer { + display: flex; + align-items: center; + gap: 32px; +} +.daggerheart.dh-style.dialog.character-creation .main-selections-container .creation-action-footer .footer-section { + display: flex; + align-items: center; + gap: 32px; +} +.daggerheart.dh-style.dialog.character-creation .main-selections-container .creation-action-footer .footer-section nav { + flex: 1; + gap: 8px; + border: 0; +} +.daggerheart.dh-style.dialog.character-creation .main-selections-container .creation-action-footer .footer-section nav a { + flex: 1; + text-align: center; + display: flex; + justify-content: center; + position: relative; + border: 1px solid light-dark(#18162e, #f3c267); + border-radius: 6px; +} +.daggerheart.dh-style.dialog.character-creation .main-selections-container .creation-action-footer .footer-section nav a .nav-section-text { + position: relative; + display: flex; + align-items: center; +} +.daggerheart.dh-style.dialog.character-creation .main-selections-container .creation-action-footer .footer-section nav a .finish-marker { + position: absolute; + align-self: center; + top: -10px; + padding: 4px; + border: 1px solid; + border-radius: 50%; + height: 20px; + width: 20px; + font-size: 14px; + display: flex; + align-items: center; + justify-content: center; + background-color: var(--color-cool-4); + content: ""; +} +.daggerheart.dh-style.dialog.character-creation .main-selections-container .creation-action-footer .footer-section nav a .finish-marker.finished { + background-color: var(--color-warm-2); +} +.daggerheart.dh-style.dialog.character-creation .main-selections-container .creation-action-footer .footer-section nav a .descriptor { + position: absolute; + bottom: -8px; + font-size: 12px; + border-radius: 8px; + width: 56px; + text-align: center; + line-height: 1; + border: 1px solid light-dark(#18162e, #f3c267); + border-radius: 6px; + color: light-dark(#efe6d8, #222); + background-image: url(../assets/parchments/dh-parchment-light.png); +} +.daggerheart.dh-style.dialog.character-creation .main-selections-container .creation-action-footer .footer-section button { + flex: 1; + height: 100%; + white-space: nowrap; +} +.daggerheart.dh-style.dialog.character-creation .creation-action-footer { + display: flex; + align-items: center; + gap: 32px; +} +.daggerheart.dh-style.dialog.character-creation .creation-action-footer button { + flex: 1; + height: 100%; + white-space: nowrap; +} .theme-light .daggerheart.levelup .tiers-container .tier-container { background-image: url('../assets/parchments/dh-parchment-light.png'); } diff --git a/styles/daggerheart.less b/styles/daggerheart.less index 9965015b..3ad972fc 100755 --- a/styles/daggerheart.less +++ b/styles/daggerheart.less @@ -8,6 +8,7 @@ @import './application.less'; @import './sheets/sheets.less'; @import './dialog.less'; +@import './characterCreation.less'; @import './levelup.less'; @import '../node_modules/@yaireo/tagify/dist/tagify.css'; @import './resources.less'; diff --git a/templates/components/card-preview.hbs b/templates/components/card-preview.hbs index ba817371..da2abb77 100644 --- a/templates/components/card-preview.hbs +++ b/templates/components/card-preview.hbs @@ -1,4 +1,7 @@ -
+
{{#if this.img}}
{{this.name}}
diff --git a/templates/sheets/character/character.hbs b/templates/sheets/character/character.hbs index 95badc15..986b16d2 100644 --- a/templates/sheets/character/character.hbs +++ b/templates/sheets/character/character.hbs @@ -1,5 +1,6 @@
+
diff --git a/templates/sheets/items/armor/settings.hbs b/templates/sheets/items/armor/settings.hbs index 5f3d749b..6a2341d9 100644 --- a/templates/sheets/items/armor/settings.hbs +++ b/templates/sheets/items/armor/settings.hbs @@ -3,7 +3,6 @@ data-tab='{{tabs.settings.id}}' data-group='{{tabs.settings.group}}' > -
{{localize tabs.settings.label}} {{localize "DAGGERHEART.Tiers.singular"}} diff --git a/templates/views/characterCreation/footer.hbs b/templates/views/characterCreation/footer.hbs new file mode 100644 index 00000000..5a0f906a --- /dev/null +++ b/templates/views/characterCreation/footer.hbs @@ -0,0 +1,29 @@ + + +{{!-- --}} \ No newline at end of file diff --git a/templates/views/characterCreation/tabs.hbs b/templates/views/characterCreation/tabs.hbs new file mode 100644 index 00000000..44065d4e --- /dev/null +++ b/templates/views/characterCreation/tabs.hbs @@ -0,0 +1,13 @@ +
+ + + +
\ No newline at end of file diff --git a/templates/views/characterCreation/tabs/equipment.hbs b/templates/views/characterCreation/tabs/equipment.hbs new file mode 100644 index 00000000..760b9e08 --- /dev/null +++ b/templates/views/characterCreation/tabs/equipment.hbs @@ -0,0 +1,9 @@ +
+
+ Test +
+
\ No newline at end of file diff --git a/templates/views/characterCreation/tabs/setup.hbs b/templates/views/characterCreation/tabs/setup.hbs new file mode 100644 index 00000000..5bee7da3 --- /dev/null +++ b/templates/views/characterCreation/tabs/setup.hbs @@ -0,0 +1,126 @@ +
+
+
+ {{localize "TYPES.Item.class"}} +
+
+ {{#> "systems/daggerheart/templates/components/card-preview.hbs" class }} + {{localize "DAGGERHEART.CharacterCreation.SelectClass"}} + {{/"systems/daggerheart/templates/components/card-preview.hbs"}} +
+ +
+ {{#> "systems/daggerheart/templates/components/card-preview.hbs" subclass disabled=(not class.img) }} + {{localize "DAGGERHEART.CharacterCreation.SelectSubclass"}} + {{/"systems/daggerheart/templates/components/card-preview.hbs"}} +
+
+
+ + {{#if (gte visibility 2)}} +
+ {{localize "DAGGERHEART.CharacterCreation.Heritage"}} +
+
+ {{#> "systems/daggerheart/templates/components/card-preview.hbs" ancestry }} + {{localize "DAGGERHEART.CharacterCreation.SelectAncestry"}} + {{/"systems/daggerheart/templates/components/card-preview.hbs"}} +
+ +
+ {{#> "systems/daggerheart/templates/components/card-preview.hbs" community }} + {{localize "DAGGERHEART.CharacterCreation.SelectCommunity"}} + {{/"systems/daggerheart/templates/components/card-preview.hbs"}} +
+
+
+ {{/if}} + + {{#if (gte visibility 3)}} +
+ {{localize "DAGGERHEART.CharacterCreation.TraitIncreases"}} {{traits.nrSelected}}/{{traits.nrTotal}} +
+
+ {{localize "DAGGERHEART.CharacterCreation.SuggestedTraits"}} +
+ {{#each suggestedTraits}} +
{{this}}
+ {{/each}} +
+ +
+
+ {{#each traits.values}} +
+
{{this.name}}
+ +
+ {{/each}} +
+
+
+ {{/if}} + + {{#if (gte visibility 4)}} +
+ {{localize "DAGGERHEART.CharacterCreation.InitialExperiences"}} {{experience.nrSelected}}/{{experience.nrTotal}} +
+ {{#each experience.values as |experience id|}} +
+ +
{{signedNumber this.value}}
+
+ {{/each}} +
+
+ {{/if}} + + {{#if (gte visibility 5)}} +
+ {{localize "TYPES.Item.domainCard"}} +
+ {{#each domainCards as |domainCard id|}} +
+ {{#> "systems/daggerheart/templates/components/card-preview.hbs" domainCard }} + {{#each @root.class.system.domains }} +
{{localize (concat "DAGGERHEART.Domains." this ".label")}}
+ {{/each}} + {{/"systems/daggerheart/templates/components/card-preview.hbs"}} +
+ {{/each}} +
+
+ {{/if}} + + {{!-- --}} +
+
\ No newline at end of file diff --git a/templates/views/characterCreation/tabs/story.hbs b/templates/views/characterCreation/tabs/story.hbs new file mode 100644 index 00000000..6573af93 --- /dev/null +++ b/templates/views/characterCreation/tabs/story.hbs @@ -0,0 +1,9 @@ +
+
+ Test +
+
\ No newline at end of file