${game.i18n.localize('SETTINGS.ReloadPromptBody')}
` + }); + + if (reload) { + await game.socket.emit('reload'); + foundry.utils.debouncedReload(); + } + this.close(); } } diff --git a/module/applications/sheets/adversary.mjs b/module/applications/sheets/adversary.mjs index b9180bf3..234d49b9 100644 --- a/module/applications/sheets/adversary.mjs +++ b/module/applications/sheets/adversary.mjs @@ -1,219 +1,12 @@ -// import DhpApplicationMixin from '../daggerheart-sheet.mjs'; - -// export class Teest extends DhpApplicationMixin(ActorSheet) { -// static documentType = "adversary"; - -// constructor(options){ -// super(options); - -// this.editMode = false; -// } - -// /** @override */ -// static get defaultOptions() { -// return foundry.utils.mergeObject(super.defaultOptions, { -// classes: ["daggerheart", "sheet", "adversary"], -// width: 600, -// height: 'auto', -// resizable: false, -// }); -// } - -// async getData() { -// const context = super.getData(); -// context.config = SYSTEM; -// context.editMode = this.editMode; -// context.title = `${this.actor.name} - ${game.i18n.localize(SYSTEM.ACTOR.adversaryTypes[this.actor.system.type].name)}`; - -// context.data = { -// description: this.object.system.description, -// motivesAndTactics: this.object.system.motivesAndTactics.join(', '), -// tier: this.object.system.tier, -// type: game.i18n.localize(SYSTEM.ACTOR.adversaryTypes[this.object.system.type].name), -// attack: { -// name: this.object.system.attack.name, -// attackModifier: this.object.system.attackModifier, -// range: this.object.system.attack.range ? game.i18n.localize(SYSTEM.GENERAL.range[this.object.system.attack.range].name) : null, -// damage: { -// value: this.object.system.attack.damage.value, -// type: this.object.system.attack.damage.type, -// typeName: this.object.system.attack.damage.type ? game.i18n.localize(SYSTEM.GENERAL.damageTypes[this.object.system.attack.damage.type].abbreviation).toLowerCase() : null, -// }, -// }, -// damageThresholds: this.object.system.damageThresholds, -// difficulty: this.object.system.difficulty, -// hp: { ...this.object.system.resources.health, lastRowIndex: Math.floor(this.object.system.resources.health.max/5)*5 }, -// stress: { ...this.object.system.resources.stress, lastRowIndex: Math.floor(this.object.system.resources.stress.max/5)*5 }, -// moves: this.object.system.moves, -// }; - -// return context; -// } - -// async _handleAction(action, event, button) { -// switch(action){ -// case 'viewMove': -// await this.viewMove(button); -// break; -// case 'addMove': -// this.addMove(); -// break; -// case 'removeMove': -// await this.removeMove(button); -// break; -// case 'toggleSlider': -// this.toggleEditMode(); -// break; -// case 'addMotive': -// await this.addMotive(); -// break; -// case 'removeMotive': -// await this.removeMotive(button); -// break; -// case 'reactionRoll': -// await this.reactionRoll(event); -// break; -// case 'attackRoll': -// await this.attackRoll(event); -// break; -// case 'addExperience': -// await this.addExperience(); -// break; -// case 'removeExperience': -// await this.removeExperience(button); -// break; -// case 'toggleHP': -// await this.toggleHP(button); -// break; -// case 'toggleStress': -// await this.toggleStress(button); -// break; -// } -// } - -// async viewMove(button){ -// const move = await fromUuid(button.dataset.move); -// move.sheet.render(true); -// } - -// async addMove(){ -// const result = await this.object.createEmbeddedDocuments("Item", [{ -// name: game.i18n.localize('DAGGERHEART.Sheets.Adversary.NewMove'), -// type: 'feature', -// }]); - -// await result[0].sheet.render(true); -// } - -// async removeMove(button){ -// await this.object.items.find(x => x.uuid === button.dataset.move).delete(); -// } - -// toggleEditMode(){ -// this.editMode = !this.editMode; -// this.render(); -// } - -// async addMotive(){ -// await this.object.update({ "system.motivesAndTactics": [...this.object.system.motivesAndTactics, ''] }); -// } - -// async removeMotive(button){ -// await this.object.update({ "system.motivesAndTactics": this.object.system.motivesAndTactics.filter((_, index) => index !== Number.parseInt(button.dataset.motive) )}); -// } - -// async reactionRoll(event){ -// const { roll, diceResults, modifiers } = await this.actor.diceRoll({ title: `${this.actor.name} - Reaction Roll`, value: 0 }, event.shiftKey); - -// const cls = getDocumentClass("ChatMessage"); -// const msg = new cls({ -// type: 'adversaryRoll', -// system: { -// roll: roll._formula, -// total: roll._total, -// modifiers: modifiers, -// diceResults: diceResults, -// }, -// content: "systems/daggerheart/templates/chat/adversary-roll.hbs", -// rolls: [roll] -// }); - -// cls.create(msg.toObject()); -// } - -// async attackRoll(event){ -// const modifier = Number.parseInt(event.currentTarget.dataset.value); - -// const { roll, diceResults, modifiers } = await this.actor.diceRoll({ title: `${this.actor.name} - Attack Roll`, value: modifier }, event.shiftKey); - -// const targets = Array.from(game.user.targets).map(x => ({ -// id: x.id, -// name: x.actor.name, -// img: x.actor.img, -// difficulty: x.actor.system.difficulty, -// evasion: x.actor.system.evasion, -// })); - -// const cls = getDocumentClass("ChatMessage"); -// const msg = new cls({ -// type: 'adversaryRoll', -// system: { -// roll: roll._formula, -// total: roll._total, -// modifiers: modifiers, -// diceResults: diceResults, -// targets: targets, -// damage: { value: event.currentTarget.dataset.damage, type: event.currentTarget.dataset.damageType }, -// }, -// content: "systems/daggerheart/templates/chat/adversary-attack-roll.hbs", -// rolls: [roll] -// }); - -// cls.create(msg.toObject()); -// } - -// async addExperience(){ -// await this.object.update({ "system.experiences": [...this.object.system.experiences, { name: 'Experience', value: 1 }] }); -// } - -// async removeExperience(button){ -// await this.object.update({ "system.experiences": this.object.system.experiences.filter((_, index) => index !== Number.parseInt(button.dataset.experience) )}); -// } - -// async toggleHP(button){ -// const index = Number.parseInt(button.dataset.index); -// const newHP = index < this.object.system.resources.health.value ? index : index+1; -// await this.object.update({ "system.resources.health.value": newHP }); -// } - -// async toggleStress(button){ -// const index = Number.parseInt(button.dataset.index); -// const newStress = index < this.object.system.resources.stress.value ? index : index+1; -// await this.object.update({ "system.resources.stress.value": newStress }); -// } -// } - import DaggerheartSheet from './daggerheart-sheet.mjs'; const { ActorSheetV2 } = foundry.applications.sheets; export default class AdversarySheet extends DaggerheartSheet(ActorSheetV2) { - constructor(options = {}) { - super(options); - - this.editMode = false; - } - static DEFAULT_OPTIONS = { tag: 'form', - classes: ['daggerheart', 'sheet', 'adversary'], - position: { width: 600 }, + classes: ['daggerheart', 'sheet', 'actor', 'dh-style', 'adversary'], + position: { width: 450, height: 1000 }, actions: { - viewMove: this.viewMove, - addMove: this.addMove, - removeMove: this.removeMove, - toggleSlider: this.toggleEditMode, - addMotive: this.addMotive, - removeMotive: this.removeMotive, reactionRoll: this.reactionRoll, attackRoll: this.attackRoll, addExperience: this.addExperience, @@ -229,54 +22,35 @@ export default class AdversarySheet extends DaggerheartSheet(ActorSheetV2) { }; static PARTS = { - form: { - id: 'feature', - template: 'systems/daggerheart/templates/sheets/adversary.hbs' + header: { template: 'systems/daggerheart/templates/sheets/actors/adversary/header.hbs' }, + tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' }, + main: { template: 'systems/daggerheart/templates/sheets/actors/adversary/main.hbs' }, + information: { template: 'systems/daggerheart/templates/sheets/actors/adversary/information.hbs' } + }; + + static TABS = { + main: { + active: true, + cssClass: '', + group: 'primary', + id: 'main', + icon: null, + label: 'DAGGERHEART.Sheets.Adversary.Tabs.Main' + }, + information: { + active: false, + cssClass: '', + group: 'primary', + id: 'information', + icon: null, + label: 'DAGGERHEART.Sheets.Adversary.Tabs.Information' } }; async _prepareContext(_options) { const context = await super._prepareContext(_options); context.document = this.document; - context.config = SYSTEM; - context.editMode = this.editMode; - context.title = `${this.actor.name} - ${game.i18n.localize(SYSTEM.ACTOR.adversaryTypes[this.actor.system.type].name)}`; - - context.data = { - description: this.document.system.description, - motivesAndTactics: this.document.system.motivesAndTactics.join(', '), - tier: this.document.system.tier, - type: game.i18n.localize(SYSTEM.ACTOR.adversaryTypes[this.document.system.type].name), - attack: { - name: this.document.system.attack.name, - attackModifier: this.document.system.attackModifier, - range: this.document.system.attack.range - ? game.i18n.localize(SYSTEM.GENERAL.range[this.document.system.attack.range].name) - : null, - damage: { - value: this.document.system.attack.damage.value, - type: this.document.system.attack.damage.type, - typeName: this.document.system.attack.damage.type - ? game.i18n - .localize( - SYSTEM.GENERAL.damageTypes[this.document.system.attack.damage.type].abbreviation - ) - .toLowerCase() - : null - } - }, - damageThresholds: this.document.system.damageThresholds, - difficulty: this.document.system.difficulty, - hp: { - ...this.document.system.resources.health, - lastRowIndex: Math.floor(this.document.system.resources.health.max / 5) * 5 - }, - stress: { - ...this.document.system.resources.stress, - lastRowIndex: Math.floor(this.document.system.resources.stress.max / 5) * 5 - }, - moves: this.document.system.moves - }; + context.tabs = super._getTabs(this.constructor.TABS); return context; } @@ -286,109 +60,43 @@ export default class AdversarySheet extends DaggerheartSheet(ActorSheetV2) { this.render(); } - static async viewMove(_, button) { - const move = await fromUuid(button.dataset.move); - move.sheet.render(true); - } - - static async addMove() { - const result = await this.document.createEmbeddedDocuments('Item', [ - { - name: game.i18n.localize('DAGGERHEART.Sheets.Adversary.NewMove'), - type: 'feature' - } - ]); - - await result[0].sheet.render(true); - } - - static async removeMove(_, button) { - await this.document.items.find(x => x.uuid === button.dataset.move).delete(); - } - - static toggleEditMode() { - this.editMode = !this.editMode; - this.render(); - } - - static async addMotive() { - await this.document.update({ 'system.motivesAndTactics': [...this.document.system.motivesAndTactics, ''] }); - } - - static async removeMotive(button) { - await this.document.update({ - 'system.motivesAndTactics': this.document.system.motivesAndTactics.filter( - (_, index) => index !== Number.parseInt(button.dataset.motive) - ) - }); - } - static async reactionRoll(event) { - const { roll, diceResults, modifiers } = await this.actor.diceRoll( - { title: `${this.actor.name} - Reaction Roll`, value: 0 }, - event.shiftKey - ); - - const cls = getDocumentClass('ChatMessage'); - const systemData = { - roll: roll._formula, - total: roll._total, - modifiers: modifiers, - diceResults: diceResults + const config = { + event: event, + title: `${this.actor.name} - Reaction Roll`, + roll: { + modifier: null, + type: 'reaction' + }, + chatMessage: { + type: 'adversaryRoll', + template: 'systems/daggerheart/templates/chat/adversary-roll.hbs', + mute: true + } }; - const msg = new cls({ - type: 'adversaryRoll', - system: systemData, - content: await foundry.applications.handlebars.renderTemplate( - 'systems/daggerheart/templates/chat/adversary-roll.hbs', - systemData - ), - rolls: [roll] - }); - - cls.create(msg.toObject()); + this.actor.diceRoll(config); } - static async attackRoll(event, button) { - const modifier = Number.parseInt(button.dataset.value); - - const { roll, dice, advantageState, modifiers } = await this.actor.diceRoll( - { title: `${this.actor.name} - Attack Roll`, value: modifier }, - event.shiftKey - ); - - const targets = Array.from(game.user.targets).map(x => ({ - id: x.id, - name: x.actor.name, - img: x.actor.img, - difficulty: x.actor.system.difficulty, - evasion: x.actor.system.evasion.value - })); - - const cls = getDocumentClass('ChatMessage'); - const systemData = { - title: button.dataset.name, - origin: this.document.id, - roll: roll._formula, - advantageState, - total: roll._total, - modifiers: modifiers, - dice: dice, - targets: targets, - damage: { value: button.dataset.damage, type: button.dataset.damageType } - }; - const msg = new cls({ - type: 'adversaryRoll', - sound: CONFIG.sounds.dice, - system: systemData, - content: await foundry.applications.handlebars.renderTemplate( - 'systems/daggerheart/templates/chat/adversary-attack-roll.hbs', - systemData - ), - rolls: [roll] - }); - - cls.create(msg.toObject()); + static async attackRoll(event) { + const { modifier, damage, name: attackName } = this.actor.system.attack, + config = { + event: event, + title: attackName, + roll: { + modifier: modifier, + type: 'action' + }, + chatMessage: { + type: 'adversaryRoll', + template: 'systems/daggerheart/templates/chat/adversary-attack-roll.hbs' + }, + damage: { + value: damage.value, + type: damage.type + }, + checkTarget: true + }; + this.actor.diceRoll(config); } static async addExperience() { diff --git a/module/applications/sheets/character.mjs b/module/applications/sheets/character.mjs new file mode 100644 index 00000000..bea918e6 --- /dev/null +++ b/module/applications/sheets/character.mjs @@ -0,0 +1,698 @@ +import { capitalize } from '../../helpers/utils.mjs'; +import DhpDeathMove from '../deathMove.mjs'; +import DhpDowntime from '../downtime.mjs'; +import AncestrySelectionDialog from '../ancestrySelectionDialog.mjs'; +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'; + +const { ActorSheetV2 } = foundry.applications.sheets; +const { TextEditor } = foundry.applications.ux; +export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { + constructor(options = {}) { + super(options); + } + + static DEFAULT_OPTIONS = { + tag: 'form', + classes: ['daggerheart', 'sheet', 'pc'], + position: { width: 810, height: 1080 }, + actions: { + attributeRoll: this.rollAttribute, + toggleMarks: this.toggleMarks, + toggleHP: this.toggleHP, + toggleStress: this.toggleStress, + toggleHope: this.toggleHope, + toggleGold: this.toggleGold, + attackRoll: this.attackRoll, + useDomainCard: this.useDomainCard, + removeCard: this.removeDomainCard, + selectClass: this.selectClass, + selectSubclass: this.selectSubclass, + selectAncestry: this.selectAncestry, + selectCommunity: this.selectCommunity, + viewObject: this.viewObject, + useItem: this.useItem, + useFeature: this.useFeature, + takeShortRest: this.takeShortRest, + takeLongRest: this.takeLongRest, + deleteItem: this.deleteItem, + addScar: this.addScar, + deleteScar: this.deleteScar, + makeDeathMove: this.makeDeathMove, + itemQuantityDecrease: (_, button) => this.setItemQuantity(button, -1), + itemQuantityIncrease: (_, button) => this.setItemQuantity(button, 1), + useAbility: this.useAbility, + useAdvancementCard: this.useAdvancementCard, + useAdvancementAbility: this.useAdvancementAbility, + toggleEquipItem: this.toggleEquipItem, + levelup: this.openLevelUp + }, + window: { + minimizable: false, + resizable: true + }, + form: { + handler: this.updateForm, + submitOnChange: true, + closeOnSubmit: false + }, + dragDrop: [ + { dragSelector: null, dropSelector: '.weapon-section' }, + { dragSelector: null, dropSelector: '.armor-section' }, + { dragSelector: '.item-list .item', dropSelector: null } + ] + }; + + static PARTS = { + form: { + id: 'character', + template: 'systems/daggerheart/templates/sheets/character/character.hbs' + } + }; + + _getTabs() { + const setActive = 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' : ''; + } + }; + + const primaryTabs = { + features: { + active: true, + cssClass: '', + group: 'primary', + id: 'features', + icon: null, + label: game.i18n.localize('DAGGERHEART.Sheets.PC.Tabs.Features') + }, + loadout: { + active: false, + cssClass: '', + group: 'primary', + id: 'loadout', + icon: null, + label: game.i18n.localize('DAGGERHEART.Sheets.PC.Tabs.Loadout') + }, + inventory: { + active: false, + cssClass: '', + group: 'primary', + id: 'inventory', + icon: null, + label: game.i18n.localize('DAGGERHEART.Sheets.PC.Tabs.Inventory') + }, + story: { + active: false, + cssClass: '', + group: 'primary', + id: 'story', + icon: null, + label: game.i18n.localize('DAGGERHEART.Sheets.PC.Tabs.Story') + } + }; + const secondaryTabs = { + foundation: { + active: true, + cssClass: '', + group: 'secondary', + id: 'foundation', + icon: null, + label: game.i18n.localize('DAGGERHEART.Sheets.PC.Tabs.Foundation') + }, + loadout: { + active: false, + cssClass: '', + group: 'secondary', + id: 'loadout', + icon: null, + label: game.i18n.localize('DAGGERHEART.Sheets.PC.Tabs.Loadout') + }, + vault: { + active: false, + cssClass: '', + group: 'secondary', + id: 'vault', + icon: null, + label: game.i18n.localize('DAGGERHEART.Sheets.PC.Tabs.Vault') + } + }; + + setActive(primaryTabs); + setActive(secondaryTabs); + + return { primary: primaryTabs, secondary: secondaryTabs }; + } + + _attachPartListeners(partId, htmlElement, options) { + super._attachPartListeners(partId, htmlElement, options); + + htmlElement.querySelector('.level-value').addEventListener('change', this.onLevelChange.bind(this)); + // To Remove when ContextMenu Handler is made + htmlElement + .querySelectorAll('[data-item-id]') + .forEach(element => element.addEventListener('contextmenu', this.editItem.bind(this))); + } + + async _prepareContext(_options) { + const context = await super._prepareContext(_options); + context.document = this.document; + context.tabs = this._getTabs(); + + context.config = SYSTEM; + + const selectedAttributes = Object.values(this.document.system.traits).map(x => x.base); + context.abilityScoreArray = JSON.parse( + await game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.General.AbilityArray) + ).reduce((acc, x) => { + const selectedIndex = selectedAttributes.indexOf(x); + if (selectedIndex !== -1) { + selectedAttributes.splice(selectedIndex, 1); + } else { + acc.push({ name: x, value: x }); + } + + return acc; + }, []); + if (!context.abilityScoreArray.includes(0)) context.abilityScoreArray.push({ name: 0, value: 0 }); + context.abilityScoresFinished = context.abilityScoreArray.every(x => x.value === 0); + + context.attributes = Object.keys(this.document.system.traits).reduce((acc, key) => { + acc[key] = { + ...this.document.system.traits[key], + name: game.i18n.localize(SYSTEM.ACTOR.abilities[key].name), + verbs: SYSTEM.ACTOR.abilities[key].verbs.map(x => game.i18n.localize(x)) + }; + + return acc; + }, {}); + + const ancestry = await this.mapFeatureType( + this.document.system.ancestry ? [this.document.system.ancestry] : [], + SYSTEM.GENERAL.objectTypes + ); + const community = await this.mapFeatureType( + this.document.system.community ? [this.document.system.community] : [], + SYSTEM.GENERAL.objectTypes + ); + const foundation = { + ancestry: ancestry[0], + community: community[0], + advancement: {} + }; + + const nrLoadoutCards = this.document.system.domainCards.loadout.length; + const loadout = await this.mapFeatureType(this.document.system.domainCards.loadout, SYSTEM.DOMAIN.cardTypes); + const vault = await this.mapFeatureType(this.document.system.domainCards.vault, SYSTEM.DOMAIN.cardTypes); + context.abilities = { + foundation: foundation, + loadout: { + top: loadout.slice(0, Math.min(2, nrLoadoutCards)), + bottom: nrLoadoutCards > 2 ? loadout.slice(2, Math.min(5, nrLoadoutCards)) : [], + nrTotal: nrLoadoutCards + }, + vault: vault.map(x => ({ + ...x, + uuid: x.uuid, + sendToLoadoutDisabled: this.document.system.domainCards.loadout.length >= 5 + })) + }; + + context.inventory = { + consumable: { + titles: { + name: game.i18n.localize('DAGGERHEART.Sheets.PC.InventoryTab.ConsumableTitle'), + quantity: game.i18n.localize('DAGGERHEART.Sheets.PC.InventoryTab.QuantityTitle') + }, + items: this.document.items.filter(x => x.type === 'consumable') + }, + miscellaneous: { + titles: { + name: game.i18n.localize('DAGGERHEART.Sheets.PC.InventoryTab.MiscellaneousTitle'), + quantity: game.i18n.localize('DAGGERHEART.Sheets.PC.InventoryTab.QuantityTitle') + }, + items: this.document.items.filter(x => x.type === 'miscellaneous') + }, + weapons: { + titles: { + name: game.i18n.localize('DAGGERHEART.Sheets.PC.InventoryTab.WeaponsTitle'), + quantity: game.i18n.localize('DAGGERHEART.Sheets.PC.InventoryTab.QuantityTitle') + }, + items: this.document.items.filter(x => x.type === 'weapon') + }, + armor: { + titles: { + name: game.i18n.localize('DAGGERHEART.Sheets.PC.InventoryTab.ArmorsTitle'), + quantity: game.i18n.localize('DAGGERHEART.Sheets.PC.InventoryTab.QuantityTitle') + }, + items: this.document.items.filter(x => x.type === 'armor') + } + }; + + if (context.inventory.length === 0) { + context.inventory = Array(1).fill(Array(5).fill([])); + } + + return context; + } + + static async updateForm(event, _, formData) { + await this.document.update(formData.object); + this.render(); + } + + async mapFeatureType(data, configType) { + return await Promise.all( + data.map(async x => { + const abilities = x.system.abilities + ? await Promise.all(x.system.abilities.map(async x => await fromUuid(x.uuid))) + : []; + + return { + ...x, + uuid: x.uuid, + system: { + ...x.system, + abilities: abilities, + type: game.i18n.localize(configType[x.system.type ?? x.type].label) + } + }; + }) + ); + } + + static async rollAttribute(event, button) { + const abilityLabel = game.i18n.localize(abilities[button.dataset.attribute].label); + const config = { + event: event, + title: game.i18n.format('DAGGERHEART.Chat.DualityRoll.AbilityCheckTitle', { + ability: abilityLabel + }), + roll: { + label: abilityLabel, + modifier: button.dataset.value + }, + chatMessage: { + template: 'systems/daggerheart/templates/chat/duality-roll.hbs' + } + }; + this.document.diceRoll(config); + + // Delete when new roll logic test done + /* const { roll, hope, fear, advantage, disadvantage, modifiers } = await this.document.dualityRoll( + { title: game.i18n.localize(abilities[button.dataset.attribute].label), value: button.dataset.value }, + event.shiftKey + ); + + const cls = getDocumentClass('ChatMessage'); + + const systemContent = new DHDualityRoll({ + title: game.i18n.format('DAGGERHEART.Chat.DualityRoll.AbilityCheckTitle', { + ability: game.i18n.localize(abilities[button.dataset.attribute].label) + }), + origin: this.document.id, + roll: roll._formula, + modifiers: modifiers, + hope: hope, + fear: fear, + advantage: advantage, + disadvantage: disadvantage + }); + + await cls.create({ + type: 'dualityRoll', + sound: CONFIG.sounds.dice, + system: systemContent, + user: game.user.id, + content: await foundry.applications.handlebars.renderTemplate( + 'systems/daggerheart/templates/chat/duality-roll.hbs', + systemContent + ), + rolls: [roll] + }); */ + } + + static async toggleMarks(_, button) { + const markValue = Number.parseInt(button.dataset.value); + const newValue = this.document.system.armor.system.marks.value >= markValue ? markValue - 1 : markValue; + await this.document.system.armor.update({ 'system.marks.value': newValue }); + } + + static async toggleHP(_, button) { + const healthValue = Number.parseInt(button.dataset.value); + const newValue = this.document.system.resources.hitPoints.value >= healthValue ? healthValue - 1 : healthValue; + await this.document.update({ 'system.resources.hitPoints.value': newValue }); + } + + static async toggleStress(_, button) { + const healthValue = Number.parseInt(button.dataset.value); + const newValue = this.document.system.resources.stress.value >= healthValue ? healthValue - 1 : healthValue; + await this.document.update({ 'system.resources.stress.value': newValue }); + } + + static async toggleHope(_, button) { + const hopeValue = Number.parseInt(button.dataset.value); + const newValue = this.document.system.resources.hope.value >= hopeValue ? hopeValue - 1 : hopeValue; + await this.document.update({ 'system.resources.hope.value': newValue }); + } + + static async toggleGold(_, button) { + const goldValue = Number.parseInt(button.dataset.value); + const goldType = button.dataset.type; + const newValue = this.document.system.gold[goldType] >= goldValue ? goldValue - 1 : goldValue; + + const update = `system.gold.${goldType}`; + await this.document.update({ [update]: newValue }); + } + + static async attackRoll(event, button) { + const weapon = await fromUuid(button.dataset.weapon); + if (!weapon) return; + weapon.use(event); + } + + static openLevelUp() { + if (!this.document.system.class.value || !this.document.system.class.subclass) { + ui.notifications.error(game.i18n.localize('DAGGERHEART.Sheets.PC.Errors.missingClassOrSubclass')); + return; + } + + new DhlevelUp(this.document).render(true); + } + + static async useDomainCard(_, button) { + const card = this.document.items.find(x => x.uuid === button.dataset.key); + + const cls = getDocumentClass('ChatMessage'); + const systemData = { + title: `${game.i18n.localize('DAGGERHEART.Chat.DomainCard.Title')} - ${capitalize(button.dataset.domain)}`, + origin: this.document.id, + img: card.img, + name: card.name, + description: card.system.effect, + actions: card.system.actions + }; + const msg = new cls({ + type: 'abilityUse', + user: game.user.id, + content: await foundry.applications.handlebars.renderTemplate( + 'systems/daggerheart/templates/chat/ability-use.hbs', + systemData + ), + system: systemData + }); + + cls.create(msg.toObject()); + } + + static async removeDomainCard(_, button) { + if (button.dataset.type === 'domainCard') { + const card = this.document.items.find(x => x.uuid === button.dataset.key); + await card.delete(); + } + } + + static async selectClass() { + (await game.packs.get('daggerheart.classes'))?.render(true); + } + + static async selectSubclass() { + (await game.packs.get('daggerheart.subclasses'))?.render(true); + } + + static async selectAncestry() { + const dialogClosed = new Promise((resolve, _) => { + new AncestrySelectionDialog(resolve).render(true); + }); + const result = await dialogClosed; + + for (var ancestry of this.document.items.filter(x => x => x.type === 'ancestry')) { + await ancestry.delete(); + } + + const createdItems = []; + for (var feature of this.document.items.filter( + x => x.type === 'feature' && x.system.type === SYSTEM.ITEM.featureTypes.ancestry.id + )) { + await feature.delete(); + } + + createdItems.push(result.data); + + await this.document.createEmbeddedDocuments('Item', createdItems); + } + + static async selectCommunity() { + (await game.packs.get('daggerheart.communities'))?.render(true); + } + + static useItem(event) { + const uuid = event.target.closest('[data-item-id]').dataset.itemId, + item = this.document.items.find(i => i.uuid === uuid); + item.use(event); + } + + static async viewObject(_, button) { + const object = await fromUuid(button.dataset.value); + if (!object) return; + + const tab = button.dataset.tab; + if (tab && object.sheet._tabs) object.sheet._tabs[0].active = tab; + + if (object.sheet.editMode) object.sheet.editMode = false; + + object.sheet.render(true); + } + + editItem(event) { + const uuid = event.target.closest('[data-item-id]').dataset.itemId, + item = this.document.items.find(i => i.uuid === uuid); + if (!item) return; + + if (item.sheet.editMode) item.sheet.editMode = false; + + item.sheet.render(true); + } + + static async takeShortRest() { + await new DhpDowntime(this.document, true).render(true); + await this.minimize(); + } + + static async takeLongRest() { + await new DhpDowntime(this.document, false).render(true); + await this.minimize(); + } + + static async addScar() { + if (this.document.system.story.scars.length === 5) return; + + await this.document.update({ + 'system.story.scars': [ + ...this.document.system.story.scars, + { name: game.i18n.localize('DAGGERHEART.Sheets.PC.NewScar'), description: '' } + ] + }); + } + + static async deleteScar(event, button) { + event.stopPropagation(); + await this.document.update({ + 'system.story.scars': this.document.system.story.scars.filter( + (_, index) => index !== Number.parseInt(button.currentTarget.dataset.scar) + ) + }); + } + + static async makeDeathMove() { + if (this.document.system.resources.hitPoints.value === this.document.system.resources.hitPoints.max) { + await new DhpDeathMove(this.document).render(true); + await this.minimize(); + } + } + + async itemUpdate(event) { + const name = event.currentTarget.dataset.item; + const item = await fromUuid($(event.currentTarget).closest('[data-item-id]')[0].dataset.itemId); + await item.update({ [name]: event.currentTarget.value }); + } + + async onLevelChange(event) { + await this.document.updateLevel(Number(event.currentTarget.value)); + this.render(); + } + + static async deleteItem(_, button) { + const item = await fromUuid($(button).closest('[data-item-id]')[0].dataset.itemId); + await item.delete(); + } + + static async setItemQuantity(button, value) { + const item = await fromUuid($(button).closest('[data-item-id]')[0].dataset.itemId); + await item.update({ 'system.quantity': Math.max(item.system.quantity + value, 1) }); + } + + static async useFeature(_, button) { + const item = await fromUuid(button.dataset.id); + + const cls = getDocumentClass('ChatMessage'); + const systemData = { + title: game.i18n.localize('DAGGERHEART.Chat.FeatureTitle'), + origin: this.document.id, + img: item.img, + name: item.name, + description: item.system.description, + actions: item.system.actions + }; + const msg = new cls({ + type: 'abilityUse', + user: game.user.id, + content: await foundry.applications.handlebars.renderTemplate( + 'systems/daggerheart/templates/chat/ability-use.hbs', + systemData + ), + system: systemData + }); + + cls.create(msg.toObject()); + } + + static async useAbility(_, button) { + const item = await fromUuid(button.dataset.feature); + const type = button.dataset.type; + + const cls = getDocumentClass('ChatMessage'); + const systemData = { + title: + type === 'ancestry' + ? game.i18n.localize('DAGGERHEART.Chat.FoundationCard.AncestryTitle') + : type === 'community' + ? game.i18n.localize('DAGGERHEART.Chat.FoundationCard.CommunityTitle') + : game.i18n.localize('DAGGERHEART.Chat.FoundationCard.SubclassFeatureTitle'), + origin: this.document.id, + img: item.img, + name: item.name, + description: item.system.description, + actions: [] + }; + const msg = new cls({ + type: 'abilityUse', + user: game.user.id, + system: systemData, + content: await foundry.applications.handlebars.renderTemplate( + 'systems/daggerheart/templates/chat/ability-use.hbs', + systemData + ) + }); + + cls.create(msg.toObject()); + } + + static async useAdvancementCard(_, button) { + const item = + button.dataset.multiclass === 'true' + ? this.document.system.multiclass.subclass + : this.document.system.class.subclass; + const ability = item.system[`${button.dataset.key}Feature`]; + const title = `${item.name} - ${game.i18n.localize(`DAGGERHEART.Sheets.PC.DomainCard.${capitalize(button.dataset.key)}Title`)}`; + + const cls = getDocumentClass('ChatMessage'); + const systemData = { + title: game.i18n.localize('DAGGERHEART.Chat.FoundationCard.SubclassFeatureTitle'), + origin: this.document.id, + name: title, + img: item.img, + description: ability.description + }; + const msg = new cls({ + type: 'abilityUse', + user: game.user.id, + system: systemData, + content: await foundry.applications.handlebars.renderTemplate( + 'systems/daggerheart/templates/chat/ability-use.hbs', + systemData + ) + }); + + cls.create(msg.toObject()); + } + + static async useAdvancementAbility(_, button) { + const item = this.document.items.find(x => x.uuid === button.dataset.id); + + const cls = getDocumentClass('ChatMessage'); + const systemData = { + title: game.i18n.localize('DAGGERHEART.Chat.FoundationCard.SubclassFeatureTitle'), + origin: this.document.id, + name: item.name, + img: item.img, + description: item.system.description + }; + const msg = new cls({ + user: game.user.id, + system: systemData, + content: await foundry.applications.handlebars.renderTemplate( + 'systems/daggerheart/templates/chat/ability-use.hbs', + systemData + ) + }); + + cls.create(msg.toObject()); + } + + static async toggleEquipItem(_, button) { + const item = this.document.items.get(button.id); + if (item.system.equipped) { + await item.update({ 'system.equipped': false }); + return; + } + + switch (item.type) { + case 'armor': + const currentArmor = this.document.system.armor; + if (currentArmor) { + await currentArmor.update({ 'system.equipped': false }); + } + + await item.update({ 'system.equipped': true }); + break; + case 'weapon': + await this.document.system.constructor.unequipBeforeEquip.bind(this.document.system)(item); + + await item.update({ 'system.equipped': true }); + break; + } + this.render(); + } + + async _onDragStart(_, event) { + super._onDragStart(event); + } + + async _onDrop(event) { + super._onDrop(event); + this._onDropItem(event, TextEditor.getDragEventData(event)); + } + + async _onDropItem(event, data) { + const item = await Item.implementation.fromDropData(data); + const itemData = item.toObject(); + + if (item.type === 'domainCard' && this.document.system.domainCards.loadout.length >= 5) { + itemData.system.inVault = true; + } + + if (this.document.uuid === item.parent?.uuid) return this._onSortItem(event, itemData); + const createdItem = await this._onDropItemCreate(itemData); + + return createdItem; + } + + async _onDropItemCreate(itemData, event) { + itemData = itemData instanceof Array ? itemData : [itemData]; + return this.document.createEmbeddedDocuments('Item', itemData); + } +} diff --git a/module/applications/sheets/daggerheart-sheet.mjs b/module/applications/sheets/daggerheart-sheet.mjs index 635d2434..4810b0a7 100644 --- a/module/applications/sheets/daggerheart-sheet.mjs +++ b/module/applications/sheets/daggerheart-sheet.mjs @@ -27,7 +27,7 @@ export default function DhpApplicationMixin(Base) { async _prepareContext(_options, objectPath = 'document') { const context = await super._prepareContext(_options); - context.source = this[objectPath].toObject(); + context.source = this[objectPath]; context.fields = this[objectPath].schema.fields; context.systemFields = this[objectPath].system ? this[objectPath].system.schema.fields : {}; diff --git a/module/applications/sheets/environment.mjs b/module/applications/sheets/environment.mjs index 8799d41a..7c59fd55 100644 --- a/module/applications/sheets/environment.mjs +++ b/module/applications/sheets/environment.mjs @@ -1,78 +1,60 @@ import DaggerheartSheet from './daggerheart-sheet.mjs'; -const { DocumentSheetV2 } = foundry.applications.api; -export default class DhpEnvironment extends DaggerheartSheet(DocumentSheetV2) { - constructor(options) { - super(options); - - this.editMode = false; - } - +const { ActorSheetV2 } = foundry.applications.sheets; +export default class DhpEnvironment extends DaggerheartSheet(ActorSheetV2) { static DEFAULT_OPTIONS = { tag: 'form', - classes: ['daggerheart', 'sheet', 'adversary', 'environment'], + classes: ['daggerheart', 'sheet', 'actor', 'dh-style', 'environment'], position: { - width: 600, - height: 'auto' + width: 450, + height: 1000 }, actions: { - toggleSlider: this.toggleSlider, - viewFeature: this.viewFeature, + addAdversary: this.addAdversary, addFeature: this.addFeature, - removeFeature: this.removeFeature, - addTone: this.addTone, - removeTone: this.removeTone, - useFeature: this.useFeature + deleteProperty: this.deleteProperty, + viewAdversary: this.viewAdversary }, form: { handler: this._updateForm, - closeOnSubmit: false, - submitOnChange: true - } + submitOnChange: true, + closeOnSubmit: false + }, + dragDrop: [{ dragSelector: null, dropSelector: '.adversary-container' }] }; - /** @override */ static PARTS = { - form: { - id: 'form', - template: 'systems/daggerheart/templates/sheets/environment.hbs' - } + header: { template: 'systems/daggerheart/templates/sheets/actors/environment/header.hbs' }, + tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' }, + main: { template: 'systems/daggerheart/templates/sheets/actors/environment/main.hbs' }, + information: { template: 'systems/daggerheart/templates/sheets/actors/environment/information.hbs' } }; - /* -------------------------------------------- */ - - /** @inheritDoc */ - get title() { - return `${game.i18n.localize('Environment')} - ${this.document.name}`; - } + static TABS = { + main: { + active: true, + cssClass: '', + group: 'primary', + id: 'main', + icon: null, + label: 'DAGGERHEART.Sheets.Environment.Tabs.Main' + }, + information: { + active: false, + cssClass: '', + group: 'primary', + id: 'information', + icon: null, + label: 'DAGGERHEART.Sheets.Environment.Tabs.Information' + } + }; async _prepareContext(_options) { - return { - title: `${this.document.name} - ${game.i18n.localize(SYSTEM.ACTOR.adversaryTypes[this.document.system.type].name)}`, - user: this.document, - source: this.document.toObject(), - fields: this.document.schema.fields, - data: { - type: game.i18n.localize(SYSTEM.ACTOR.adversaryTypes[this.document.system.type].name), - features: this.document.items.reduce((acc, x) => { - if (x.type === 'feature') { - const feature = x.toObject(); - acc.push({ - ...feature, - system: { - ...feature.system, - actionType: game.i18n.localize(SYSTEM.ITEM.actionTypes[feature.system.actionType].name) - }, - uuid: x.uuid - }); - } + const context = await super._prepareContext(_options); + context.document = this.document; + context.tabs = super._getTabs(this.constructor.TABS); - return acc; - }, []) - }, - editMode: this.editMode, - config: SYSTEM - }; + return context; } static async _updateForm(event, _, formData) { @@ -80,60 +62,41 @@ export default class DhpEnvironment extends DaggerheartSheet(DocumentSheetV2) { this.render(); } - static toggleSlider() { - this.editMode = !this.editMode; + static async addAdversary() { + await this.document.update({ + [`system.potentialAdversaries.${foundry.utils.randomID()}.label`]: game.i18n.localize( + 'DAGGERHEART.Sheets.Environment.newAdversary' + ) + }); this.render(); } - static async viewFeature(_, button) { - const move = await fromUuid(button.dataset.feature); - move.sheet.render(true); - } - static async addFeature() { - const result = await this.document.createEmbeddedDocuments('Item', [ - { - name: game.i18n.localize('DAGGERHEART.Sheets.Environment.NewFeature'), - type: 'feature' - } - ]); - - await result[0].sheet.render(true); + ui.notifications.error('Not Implemented yet. Awaiting datamodel rework'); } - static async removeFeature(_, button) { - await this.document.items.find(x => x.uuid === button.dataset.feature).delete(); + static async deleteProperty(_, target) { + await this.document.update({ [`${target.dataset.path}.-=${target.id}`]: null }); + this.render(); } - static async addTone() { - await this.document.update({ 'system.toneAndFeel': [...this.document.system.toneAndFeel, ''] }); + static async viewAdversary(_, button) { + const adversary = foundry.utils.getProperty( + this.document.system.potentialAdversaries, + `${button.dataset.potentialAdversary}.adversaries.${button.dataset.adversary}` + ); + adversary.sheet.render(true); } - static async removeTone(button) { - await this.document.update({ - 'system.toneAndFeel': this.document.system.toneAndFeel.filter( - (_, index) => index !== Number.parseInt(button.dataset.tone) - ) - }); - } - - static async useFeature(_, button) { - const item = this.document.items.find(x => x.uuid === button.dataset.feature); - - const cls = getDocumentClass('ChatMessage'); - const msg = new cls({ - user: game.user.id, - content: await foundry.applications.handlebars.renderTemplate( - 'systems/daggerheart/templates/chat/ability-use.hbs', - { - title: game.i18n.format('DAGGERHEART.Chat.EnvironmentTitle', { - actionType: button.dataset.actionType - }), - card: { name: item.name, img: item.img, description: item.system.description } - } - ) - }); - - cls.create(msg.toObject()); + async _onDrop(event) { + const data = TextEditor.getDragEventData(event); + const item = await fromUuid(data.uuid); + if (item.type === 'adversary') { + const target = event.target.closest('.adversary-container'); + const path = `system.potentialAdversaries.${target.dataset.potentialAdversary}.adversaries.${item.id}`; + await this.document.update({ + [path]: item.uuid + }); + } } } diff --git a/module/applications/sheets/item.mjs b/module/applications/sheets/item.mjs new file mode 100644 index 00000000..1b8c416b --- /dev/null +++ b/module/applications/sheets/item.mjs @@ -0,0 +1,132 @@ +import DhpApplicationMixin from './daggerheart-sheet.mjs'; +import DHActionConfig from '../config/Action.mjs'; +import { actionsTypes } from '../../data/_module.mjs'; + +export default function DHItemMixin(Base) { + return class DHItemSheetV2 extends DhpApplicationMixin(Base) { + constructor(options = {}) { + super(options); + } + + static DEFAULT_OPTIONS = { + tag: 'form', + classes: ['daggerheart', 'sheet', 'item', 'dh-style'], + position: { width: 600 }, + form: { + handler: this.updateForm, + submitOnChange: true, + closeOnSubmit: false + }, + actions: { + addAction: this.addAction, + editAction: this.editAction, + removeAction: this.removeAction + } + }; + + static TABS = { + description: { + active: true, + cssClass: '', + group: 'primary', + id: 'description', + icon: null, + label: 'DAGGERHEART.Sheets.Feature.Tabs.Description' + }, + actions: { + active: false, + cssClass: '', + group: 'primary', + id: 'actions', + icon: null, + label: 'DAGGERHEART.Sheets.Feature.Tabs.Actions' + }, + settings: { + active: false, + cssClass: '', + group: 'primary', + id: 'settings', + icon: null, + label: 'DAGGERHEART.Sheets.Feature.Tabs.Settings' + } + }; + + async _prepareContext(_options) { + const context = await super._prepareContext(_options); + context.document = this.document; + context.config = CONFIG.daggerheart; + context.tabs = super._getTabs(this.constructor.TABS); + + return context; + } + + static async updateForm(event, _, formData) { + await this.document.update(formData.object); + this.render(); + } + + static async selectActionType() { + const content = await foundry.applications.handlebars.renderTemplate( + 'systems/daggerheart/templates/views/actionType.hbs', + { types: SYSTEM.ACTIONS.actionTypes } + ), + title = 'Select Action Type', + type = 'form', + data = {}; + return Dialog.prompt({ + title, + label: title, + content, + type, + callback: html => { + const form = html[0].querySelector('form'), + fd = new foundry.applications.ux.FormDataExtended(form); + foundry.utils.mergeObject(data, fd.object, { inplace: true }); + // if (!data.name?.trim()) data.name = game.i18n.localize(SYSTEM.ACTIONS.actionTypes[data.type].name); + return data; + }, + rejectClose: false + }); + } + + static async addAction() { + const actionType = await DHItemSheetV2.selectActionType(), + actionIndexes = this.document.system.actions.map(x => x._id.split('-')[2]).sort((a, b) => a - b); + try { + const cls = actionsTypes[actionType?.type] ?? actionsTypes.attack, + action = new cls( + { + // id: `${this.document.id}-Action-${actionIndexes.length > 0 ? actionIndexes[0] + 1 : 1}` + _id: foundry.utils.randomID(), + type: actionType.type, + name: game.i18n.localize(SYSTEM.ACTIONS.actionTypes[actionType.type].name), + ...cls.getSourceConfig(this.document) + }, + { + parent: this.document + } + ); + await this.document.update({ 'system.actions': [...this.document.system.actions, action] }); + await new DHActionConfig(this.document.system.actions[this.document.system.actions.length - 1]).render( + true + ); + } catch (error) { + console.log(error); + } + } + + static async editAction(_, button) { + const action = this.document.system.actions[button.dataset.index]; + await new DHActionConfig(action).render(true); + } + + static async removeAction(event, button) { + event.stopPropagation(); + await this.document.update({ + 'system.actions': this.document.system.actions.filter( + (_, index) => index !== Number.parseInt(button.dataset.index) + ) + }); + } + }; +} diff --git a/module/applications/sheets/items/armor.mjs b/module/applications/sheets/items/armor.mjs index b9759917..43e6dce9 100644 --- a/module/applications/sheets/items/armor.mjs +++ b/module/applications/sheets/items/armor.mjs @@ -1,16 +1,9 @@ -import DaggerheartSheet from '../daggerheart-sheet.mjs'; +import DHItemSheetV2 from '../item.mjs'; const { ItemSheetV2 } = foundry.applications.sheets; -export default class ArmorSheet extends DaggerheartSheet(ItemSheetV2) { +export default class ArmorSheet extends DHItemSheetV2(ItemSheetV2) { static DEFAULT_OPTIONS = { - tag: 'form', - classes: ['daggerheart', 'sheet', 'item', 'dh-style', 'armor'], - position: { width: 600 }, - form: { - handler: this.updateForm, - submitOnChange: true, - closeOnSubmit: false - }, + classes: ['armor'], dragDrop: [{ dragSelector: null, dropSelector: null }] }; @@ -18,42 +11,13 @@ export default class ArmorSheet extends DaggerheartSheet(ItemSheetV2) { header: { template: 'systems/daggerheart/templates/sheets/items/armor/header.hbs' }, tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' }, description: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-description.hbs' }, + actions: { + template: 'systems/daggerheart/templates/sheets/global/tabs/tab-actions.hbs', + scrollable: ['.actions'] + }, settings: { template: 'systems/daggerheart/templates/sheets/items/armor/settings.hbs', scrollable: ['.settings'] } }; - - static TABS = { - description: { - active: true, - cssClass: '', - group: 'primary', - id: 'description', - icon: null, - label: 'DAGGERHEART.Sheets.Feature.Tabs.Description' - }, - settings: { - active: false, - cssClass: '', - group: 'primary', - id: 'settings', - icon: null, - label: 'DAGGERHEART.Sheets.Feature.Tabs.Settings' - } - }; - - async _prepareContext(_options) { - const context = await super._prepareContext(_options); - context.document = this.document; - context.config = CONFIG.daggerheart; - context.tabs = super._getTabs(this.constructor.TABS); - - return context; - } - - static async updateForm(event, _, formData) { - await this.document.update(formData.object); - this.render(); - } } diff --git a/module/applications/sheets/items/class.mjs b/module/applications/sheets/items/class.mjs index 2241a715..c8f5c1e1 100644 --- a/module/applications/sheets/items/class.mjs +++ b/module/applications/sheets/items/class.mjs @@ -1,5 +1,5 @@ +import { tagifyElement } from '../../../helpers/utils.mjs'; import DaggerheartSheet from '../daggerheart-sheet.mjs'; -import Tagify from '@yaireo/tagify'; const { ItemSheetV2 } = foundry.applications.sheets; const { TextEditor } = foundry.applications.ux; @@ -11,8 +11,8 @@ export default class ClassSheet extends DaggerheartSheet(ItemSheetV2) { actions: { removeSubclass: this.removeSubclass, viewSubclass: this.viewSubclass, - removeFeature: this.removeFeature, - viewFeature: this.viewFeature, + deleteFeature: this.deleteFeature, + editFeature: this.editFeature, removeItem: this.removeItem, viewItem: this.viewItem, removePrimaryWeapon: this.removePrimaryWeapon, @@ -72,55 +72,14 @@ export default class ClassSheet extends DaggerheartSheet(ItemSheetV2) { super._attachPartListeners(partId, htmlElement, options); const domainInput = htmlElement.querySelector('.domain-input'); - const domainTagify = new Tagify(domainInput, { - tagTextProp: 'name', - enforceWhitelist: true, - whitelist: Object.keys(SYSTEM.DOMAIN.domains).map(key => { - const domain = SYSTEM.DOMAIN.domains[key]; - return { - value: key, - name: game.i18n.localize(domain.label), - src: domain.src, - background: domain.background - }; - }), - maxTags: 2, - callbacks: { invalid: this.onAddTag }, - dropdown: { - mapValueTo: 'name', - searchKeys: ['name'], - enabled: 0, - maxItems: 20, - closeOnSelect: true, - highlightFirst: false - }, - templates: { - tag(tagData) { - //z-index: unset; background-image: ${tagData.background}; Maybe a domain specific background for the chips? - return `+ ${game.i18n.localize("AreYouSure")} + ${game.i18n.format("SIDEBAR.DeleteWarning", { type })} +
`; + + return foundry.applications.api.DialogV2.confirm({ + content, + yes: { callback: () => this.delete(operation) }, + window: { + icon: "fa-solid fa-trash", + title: `${game.i18n.format("DOCUMENT.Delete", { type })}: ${this.name}` + }, + ...options + }); + } + + /** + * Gets the default new name for a Document + * @param {object} collection - Collection of Documents + * @returns {string} + */ + static defaultName(collection) { + const documentName = this.metadata.name; + const takenNames = new Set(); + for (const document of collection) takenNames.add(document.name); + + const config = CONFIG.daggerheart.pseudoDocuments[documentName]; + const baseName = game.i18n.localize(config.label); + let name = baseName; + let index = 1; + while (takenNames.has(name)) name = `${baseName} (${++index})`; + return name; + } + } + + return PseudoDocumentWithSheets; +} diff --git a/module/data/pseudo-documents/feature/_module.mjs b/module/data/pseudo-documents/feature/_module.mjs new file mode 100644 index 00000000..794f5d27 --- /dev/null +++ b/module/data/pseudo-documents/feature/_module.mjs @@ -0,0 +1,2 @@ +export { default as BaseFeatureData } from './baseFeatureData.mjs'; +export { default as WeaponFeature } from './weaponFeature.mjs'; diff --git a/module/data/pseudo-documents/feature/baseFeatureData.mjs b/module/data/pseudo-documents/feature/baseFeatureData.mjs new file mode 100644 index 00000000..61d1468d --- /dev/null +++ b/module/data/pseudo-documents/feature/baseFeatureData.mjs @@ -0,0 +1,24 @@ +import PseudoDocument from '../base/pseudoDocument.mjs'; + +export default class BaseFeatureData extends PseudoDocument { + /**@inheritdoc */ + static get metadata() { + return foundry.utils.mergeObject( + super.metadata, + { + name: 'feature', + embedded: {}, + //sheetClass: null //TODO: define feature-sheet + }, + { inplace: false } + ); + } + + static defineSchema() { + const { fields } = foundry.data; + const schema = super.defineSchema(); + return Object.assign(schema, { + subtype: new fields.StringField({ initial: 'test' }) + }); + } +} diff --git a/module/data/pseudo-documents/feature/weaponFeature.mjs b/module/data/pseudo-documents/feature/weaponFeature.mjs new file mode 100644 index 00000000..c8039ede --- /dev/null +++ b/module/data/pseudo-documents/feature/weaponFeature.mjs @@ -0,0 +1,6 @@ +import BaseFeatureData from './baseFeatureData.mjs'; + +export default class WeaponFeature extends BaseFeatureData { + /**@override */ + static TYPE = 'weapon'; +} diff --git a/module/data/settings/VariantRules.mjs b/module/data/settings/VariantRules.mjs index 2a1f948d..7d28a1d7 100644 --- a/module/data/settings/VariantRules.mjs +++ b/module/data/settings/VariantRules.mjs @@ -1,11 +1,14 @@ export default class DhVariantRules extends foundry.abstract.DataModel { + static LOCALIZATION_PREFIXES = ['DAGGERHEART.Settings.VariantRules']; + static defineSchema() { const fields = foundry.data.fields; return { actionTokens: new fields.SchemaField({ enabled: new fields.BooleanField({ required: true, initial: false }), tokens: new fields.NumberField({ required: true, integer: true, initial: 3 }) - }) + }), + useCoins: new fields.BooleanField({ initial: false }) }; } diff --git a/module/data/subclass.mjs b/module/data/subclass.mjs deleted file mode 100644 index 938d290c..00000000 --- a/module/data/subclass.mjs +++ /dev/null @@ -1,57 +0,0 @@ -import { getTier } from '../helpers/utils.mjs'; -import featuresSchema from './interface/featuresSchema.mjs'; -import DaggerheartFeature from './feature.mjs'; - -export default class DhpSubclass extends foundry.abstract.TypeDataModel { - static defineSchema() { - const fields = foundry.data.fields; - return { - description: new fields.HTMLField({}), - spellcastingTrait: new fields.StringField({ - choices: SYSTEM.ACTOR.abilities, - integer: false, - nullable: true, - initial: null - }), - foundationFeature: new fields.SchemaField({ - description: new fields.HTMLField({}), - abilities: new fields.ArrayField( - new fields.SchemaField({ - name: new fields.StringField({}), - img: new fields.StringField({}), - uuid: new fields.StringField({}) - }) - ) - }), - specializationFeature: new fields.SchemaField({ - unlocked: new fields.BooleanField({ initial: false }), - tier: new fields.NumberField({ initial: null, nullable: true, integer: true }), - description: new fields.HTMLField({}), - abilities: new fields.ArrayField( - new fields.SchemaField({ - name: new fields.StringField({}), - img: new fields.StringField({}), - uuid: new fields.StringField({}) - }) - ) - }), - masteryFeature: new fields.SchemaField({ - unlocked: new fields.BooleanField({ initial: false }), - tier: new fields.NumberField({ initial: null, nullable: true, integer: true }), - description: new fields.HTMLField({}), - abilities: new fields.ArrayField( - new fields.SchemaField({ - name: new fields.StringField({}), - img: new fields.StringField({}), - uuid: new fields.StringField({}) - }) - ) - }), - multiclass: new fields.NumberField({ initial: null, nullable: true, integer: true }) - }; - } - - get multiclassTier() { - return getTier(this.multiclass); - } -} diff --git a/module/data/weapon.mjs b/module/data/weapon.mjs deleted file mode 100644 index a0ef07b2..00000000 --- a/module/data/weapon.mjs +++ /dev/null @@ -1,54 +0,0 @@ -export default class DhpWeapon extends foundry.abstract.TypeDataModel { - static defineSchema() { - const fields = foundry.data.fields; - return { - equipped: new fields.BooleanField({ initial: false }), - inventoryWeapon: new fields.NumberField({ initial: null, nullable: true, integer: true }), - secondary: new fields.BooleanField({ initial: false }), - trait: new fields.StringField({ choices: SYSTEM.ACTOR.abilities, integer: false, initial: 'agility' }), - range: new fields.StringField({ choices: SYSTEM.GENERAL.range, integer: false, initial: 'melee' }), - damage: new fields.SchemaField({ - value: new fields.StringField({ initial: 'd6' }), - type: new fields.StringField({ - choices: SYSTEM.GENERAL.damageTypes, - integer: false, - initial: 'physical' - }) - }), - burden: new fields.StringField({ choices: SYSTEM.GENERAL.burden, integer: false, initial: 'oneHanded' }), - feature: new fields.StringField({ choices: SYSTEM.ITEM.weaponFeatures, integer: false, blank: true }), - quantity: new fields.NumberField({ initial: 1, integer: true }), - description: new fields.HTMLField({}) - }; - } - - prepareDerivedData() { - if (this.parent.parent) { - this.applyEffects(); - } - } - - applyEffects() { - const effects = this.parent.parent.system.effects; - for (var key in effects) { - const effectType = effects[key]; - for (var effect of effectType) { - switch (key) { - case SYSTEM.EFFECTS.effectTypes.reach.id: - if ( - SYSTEM.GENERAL.range[this.range].distance < - SYSTEM.GENERAL.range[effect.valueData.value].distance - ) { - this.range = effect.valueData.value; - } - - break; - // case SYSTEM.EFFECTS.effectTypes.damage.id: - - // if(this.damage.type === 'physical') this.damage.value = (`${this.damage.value} + ${this.parent.parent.system.levelData.currentLevel}`); - // break; - } - } - } - } -} diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index bc116550..fea4a426 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -3,6 +3,7 @@ import NpcRollSelectionDialog from '../applications/npcRollSelectionDialog.mjs'; import RollSelectionDialog from '../applications/rollSelectionDialog.mjs'; import { GMUpdateEvent, socketEvent } from '../helpers/socket.mjs'; import { setDiceSoNiceForDualityRoll } from '../helpers/utils.mjs'; +import DHDualityRoll from '../data/chat-message/dualityRoll.mjs'; export default class DhpActor extends Actor { async _preCreate(data, options, user) { @@ -10,7 +11,7 @@ export default class DhpActor extends Actor { // Configure prototype token settings const prototypeToken = {}; - if (this.type === 'pc') + if (this.type === 'character') Object.assign(prototypeToken, { sight: { enabled: true }, actorLink: true, @@ -28,7 +29,7 @@ export default class DhpActor extends Actor { } async updateLevel(newLevel) { - if (this.type !== 'pc' || newLevel === this.system.levelData.level.changed) return; + if (this.type !== 'character' || newLevel === this.system.levelData.level.changed) return; if (newLevel > this.system.levelData.level.current) { await this.update({ 'system.levelData.level.changed': newLevel }); @@ -39,17 +40,65 @@ export default class DhpActor extends Actor { return acc; }, {}); - const domainCards = Object.keys(this.system.levelData.levelups) + const domainCards = []; + const experiences = []; + const subclassFeatureState = { class: null, multiclass: null }; + let multiclass = null; + Object.keys(this.system.levelData.levelups) .filter(x => x > newLevel) - .flatMap(levelKey => { + .forEach(levelKey => { const level = this.system.levelData.levelups[levelKey]; const achievementCards = level.achievements.domainCards.map(x => x.itemUuid); const advancementCards = level.selections.filter(x => x.type === 'domainCard').map(x => x.itemUuid); - return [...achievementCards, ...advancementCards]; + domainCards.push(...achievementCards, ...advancementCards); + experiences.push(...Object.keys(level.achievements.experiences)); + + const subclass = level.selections.find(x => x.type === 'subclass'); + if (subclass) { + const path = subclass.secondaryData.isMulticlass === 'true' ? 'multiclass' : 'class'; + const subclassState = Number(subclass.secondaryData.featureState) - 1; + subclassFeatureState[path] = subclassFeatureState[path] + ? Math.min(subclassState, subclassFeatureState[path]) + : subclassState; + } + + multiclass = level.selections.find(x => x.type === 'multiclass'); }); - for (var domainCard of domainCards) { - const itemCard = await this.items.find(x => x.uuid === domainCard); + if (experiences.length > 0) { + this.update({ + 'system.experiences': experiences.reduce((acc, key) => { + acc[`-=${key}`] = null; + return acc; + }, {}) + }); + } + + if (subclassFeatureState.class) { + this.system.class.subclass.update({ 'system.featureState': subclassFeatureState.class }); + } + + if (subclassFeatureState.multiclass) { + this.system.multiclass.subclass.update({ 'system.featureState': subclassFeatureState.multiclass }); + } + + if (multiclass) { + const multiclassSubclass = this.items.find(x => x.type === 'subclass' && x.system.isMulticlass); + const multiclassItem = this.items.find(x => x.uuid === multiclass.itemUuid); + + multiclassSubclass.delete(); + multiclassItem.delete(); + + this.update({ + 'system.multiclass': { + value: null, + subclass: null + } + }); + } + + for (let domainCard of domainCards) { + const itemCard = this.items.find(x => x.uuid === domainCard); itemCard.delete(); } @@ -71,6 +120,94 @@ export default class DhpActor extends Actor { const levelups = {}; for (var levelKey of Object.keys(levelupData)) { const level = levelupData[levelKey]; + + for (var experienceKey in level.achievements.experiences) { + const experience = level.achievements.experiences[experienceKey]; + await this.update({ + [`system.experiences.${experienceKey}`]: { + description: experience.name, + value: experience.modifier + } + }); + } + + let multiclass = null; + const domainCards = []; + const subclassFeatureState = { class: null, multiclass: null }; + const selections = []; + for (var optionKey of Object.keys(level.choices)) { + const selection = level.choices[optionKey]; + for (var checkboxNr of Object.keys(selection)) { + const checkbox = selection[checkboxNr]; + + if (checkbox.type === 'multiclass') { + multiclass = { + ...checkbox, + level: Number(levelKey), + optionKey: optionKey, + checkboxNr: Number(checkboxNr) + }; + } else if (checkbox.type === 'domainCard') { + domainCards.push({ + ...checkbox, + level: Number(levelKey), + optionKey: optionKey, + checkboxNr: Number(checkboxNr) + }); + } else { + if (checkbox.type === 'subclass') { + const path = checkbox.secondaryData.isMulticlass === 'true' ? 'multiclass' : 'class'; + subclassFeatureState[path] = Math.max( + Number(checkbox.secondaryData.featureState), + subclassFeatureState[path] + ); + } + + selections.push({ + ...checkbox, + level: Number(levelKey), + optionKey: optionKey, + checkboxNr: Number(checkboxNr) + }); + } + } + } + + if (multiclass) { + const subclassItem = await foundry.utils.fromUuid(multiclass.secondaryData.subclass); + const subclassData = subclassItem.toObject(); + const multiclassItem = await foundry.utils.fromUuid(multiclass.data[0]); + const multiclassData = multiclassItem.toObject(); + + const embeddedItem = await this.createEmbeddedDocuments('Item', [ + { + ...multiclassData, + system: { + ...multiclassData.system, + domains: [multiclass.secondaryData.domain], + isMulticlass: true + } + } + ]); + + await this.createEmbeddedDocuments('Item', [ + { + ...subclassData, + system: { + ...subclassData.system, + isMulticlass: true + } + } + ]); + selections.push({ ...multiclass, itemUuid: embeddedItem[0].uuid }); + } + + for (var domainCard of domainCards) { + const item = await foundry.utils.fromUuid(domainCard.data[0]); + const embeddedItem = await this.createEmbeddedDocuments('Item', [item.toObject()]); + selections.push({ ...domainCard, itemUuid: embeddedItem[0].uuid }); + } + const achievementDomainCards = []; for (var card of Object.values(level.achievements.domainCards)) { const item = await foundry.utils.fromUuid(card.uuid); @@ -79,27 +216,14 @@ export default class DhpActor extends Actor { achievementDomainCards.push(card); } - const selections = []; - for (var optionKey of Object.keys(level.choices)) { - const selection = level.choices[optionKey]; - for (var checkboxNr of Object.keys(selection)) { - const checkbox = selection[checkboxNr]; - let itemUuid = null; + if (subclassFeatureState.class) { + await this.system.class.subclass.update({ 'system.featureState': subclassFeatureState.class }); + } - if (checkbox.type === 'domainCard') { - const item = await foundry.utils.fromUuid(checkbox.data[0]); - const embeddedItem = await this.createEmbeddedDocuments('Item', [item.toObject()]); - itemUuid = embeddedItem[0].uuid; - } - - selections.push({ - ...checkbox, - level: Number(levelKey), - optionKey: optionKey, - checkboxNr: Number(checkboxNr), - itemUuid - }); - } + if (subclassFeatureState.multiclass) { + await this.system.multiclass.subclass.update({ + 'system.featureState': subclassFeatureState.multiclass + }); } levelups[levelKey] = { @@ -123,154 +247,176 @@ export default class DhpActor extends Actor { }); } - async diceRoll(modifier, shiftKey) { - if (this.type === 'pc') { - return await this.dualityRoll(modifier, shiftKey); - } else { - return await this.npcRoll(modifier, shiftKey); - } - } - - async npcRoll(modifier, shiftKey) { - let advantage = null; - - const modifiers = [ - { - value: Number.parseInt(modifier.value), - label: modifier.value >= 0 ? `+${modifier.value}` : `-${modifier.value}`, - title: modifier.title - } - ]; - if (!shiftKey) { - const dialogClosed = new Promise((resolve, _) => { - new NpcRollSelectionDialog(this.system.experiences, resolve).render(true); - }); - const result = await dialogClosed; - - advantage = result.advantage; - result.experiences.forEach(x => - modifiers.push({ - value: x.value, - label: x.value >= 0 ? `+${x.value}` : `-${x.value}`, - title: x.description - }) - ); - } - - const roll = Roll.create( - `${advantage === true || advantage === false ? 2 : 1}d20${advantage === true ? 'kh' : advantage === false ? 'kl' : ''} ${modifiers.map(x => `+ ${x.value}`).join(' ')}` - ); - let rollResult = await roll.evaluate(); - const dice = []; - for (var i = 0; i < rollResult.terms.length; i++) { - const term = rollResult.terms[i]; - if (term.faces) { - dice.push({ type: `d${term.faces}`, rolls: term.results.map(x => ({ value: x.result })) }); - } - } - - // There is Only ever one dice term here - return { roll, dice: dice[0], modifiers, advantageState: advantage === true ? 1 : advantage === false ? 2 : 0 }; - } - - async dualityRoll(modifier, shiftKey, bonusDamage = []) { + /** + * @param {object} config + * @param {Event} config.event + * @param {string} config.title + * @param {object} config.roll + * @param {number} config.roll.modifier + * @param {boolean} [config.roll.simple=false] + * @param {string} [config.roll.type] + * @param {number} [config.roll.difficulty] + * @param {any} [config.damage] + * @param {object} [config.chatMessage] + * @param {string} config.chatMessage.template + * @param {boolean} [config.chatMessage.mute] + * @param {boolean} [config.checkTarget] + */ + async diceRoll(config) { let hopeDice = 'd12', fearDice = 'd12', - advantageDice = null, - disadvantageDice = null, - bonusDamageString = ''; + advantageDice = 'd6', + disadvantageDice = 'd6', + advantage = config.event.altKey ? true : config.event.ctrlKey ? false : null, + targets, + damage = config.damage, + modifiers = this.formatRollModifier(config.roll), + rollConfig, + formula, + hope, + fear; - const modifiers = - modifier.value !== null - ? [ - { - value: modifier.value ? Number.parseInt(modifier.value) : 0, - label: - modifier.value >= 0 - ? `${modifier.title} +${modifier.value}` - : `${modifier.title} ${modifier.value}`, - title: modifier.title - } - ] - : []; - if (!shiftKey) { + if (!config.event.shiftKey && !config.event.altKey && !config.event.ctrlKey) { const dialogClosed = new Promise((resolve, _) => { - new RollSelectionDialog( - this.system.experiences, - bonusDamage, - this.system.resources.hope.value, - resolve - ).render(true); + this.type === 'character' + ? new RollSelectionDialog( + this.system.experiences, + this.system.resources.hope.value, + resolve + ).render(true) + : new NpcRollSelectionDialog(this.system.experiences, resolve).render(true); }); - const result = await dialogClosed; - (hopeDice = result.hope), - (fearDice = result.fear), - (advantageDice = result.advantage), - (disadvantageDice = result.disadvantage); - result.experiences.forEach(x => + rollConfig = await dialogClosed; + + advantage = rollConfig.advantage; + hopeDice = rollConfig.hope; + fearDice = rollConfig.fear; + + rollConfig.experiences.forEach(x => modifiers.push({ value: x.value, label: x.value >= 0 ? `+${x.value}` : `-${x.value}`, title: x.description }) ); - bonusDamageString = result.bonusDamage; - const automateHope = await game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Automation.Hope); + if (this.type === 'character') { + const automateHope = await game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Automation.Hope); - if (automateHope && result.hopeUsed) { - await this.update({ - 'system.resources.hope.value': this.system.resources.hope.value - result.hopeUsed - }); + if (automateHope && result.hopeUsed) { + await this.update({ + 'system.resources.hope.value': this.system.resources.hope.value - result.hopeUsed + }); + } } } - const roll = new Roll( - `1${hopeDice} + 1${fearDice}${advantageDice ? ` + 1${advantageDice}` : disadvantageDice ? ` - 1${disadvantageDice}` : ''} ${modifiers.map(x => `+ ${x.value}`).join(' ')}` - ); - let rollResult = await roll.evaluate(); - setDiceSoNiceForDualityRoll(rollResult, advantageDice, disadvantageDice); - const hope = rollResult.dice[0].results[0].result; - const fear = rollResult.dice[1].results[0].result; - const advantage = advantageDice ? rollResult.dice[2].results[0].result : null; - const disadvantage = disadvantageDice ? rollResult.dice[2].results[0].result : null; - - if (disadvantage) { - rollResult = { ...rollResult, total: rollResult.total - Math.max(hope, disadvantage) }; - } - if (advantage) { - rollResult = { ...rollResult, total: 'Select Hope Die' }; + if (this.type === 'character') { + formula = `1${hopeDice} + 1${fearDice}${advantage === true ? ` + 1d6` : advantage === false ? ` - 1d6` : ''}`; + } else { + formula = `${advantage === true || advantage === false ? 2 : 1}d20${advantage === true ? 'kh' : advantage === false ? 'kl' : ''}`; } + formula += ` ${modifiers.map(x => `+ ${x.value}`).join(' ')}`; + const roll = await Roll.create(formula).evaluate(); + const dice = roll.dice.flatMap(dice => ({ + denomination: dice.denomination, + number: dice.number, + total: dice.total, + results: dice.results.map(result => ({ result: result.result, discarded: !result.active })) + })); - const automateHope = await game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Automation.Hope); - if (automateHope && hope > fear) { - await this.update({ - 'system.resources.hope.value': Math.min( - this.system.resources.hope.value + 1, - this.system.resources.hope.max - ) - }); - } - - if (automateHope && hope === fear) { - await this.update({ - 'system.resources': { - 'hope.value': Math.min(this.system.resources.hope.value + 1, this.system.resources.hope.max), - 'stress.value': Math.max(this.system.resources.stress.value - 1, 0) + if (this.type === 'character') { + setDiceSoNiceForDualityRoll(roll, advantage); + hope = roll.dice[0].results[0].result; + fear = roll.dice[1].results[0].result; + if ( + game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Automation.Hope) && + config.roll.type === 'action' + ) { + if (hope > fear) { + await this.update({ + 'system.resources.hope.value': Math.min( + this.system.resources.hope.value + 1, + this.system.resources.hope.max + ) + }); + } else if (hope === fear) { + await this.update({ + 'system.resources': { + 'hope.value': Math.min( + this.system.resources.hope.value + 1, + this.system.resources.hope.max + ), + 'stress.value': Math.max(this.system.resources.stress.value - 1, 0) + } + }); } + } + } + + if (config.checkTarget) { + targets = Array.from(game.user.targets).map(x => { + const target = { + id: x.id, + name: x.actor.name, + img: x.actor.img, + difficulty: x.actor.system.difficulty, + evasion: x.actor.system.evasion?.value + }; + + target.hit = target.difficulty ? roll.total >= target.difficulty : roll.total >= target.evasion; + + return target; }); } - return { - roll, - rollResult, - hope: { dice: hopeDice, value: hope }, - fear: { dice: fearDice, value: fear }, - advantage: { dice: advantageDice, value: advantage }, - disadvantage: { dice: disadvantageDice, value: disadvantage }, - modifiers: modifiers, - bonusDamageString - }; + if (config.chatMessage) { + const configRoll = { + title: config.title, + origin: this.id, + dice, + roll, + modifiers: modifiers.filter(x => x.label), + advantageState: advantage + }; + if (this.type === 'character') { + configRoll.hope = { dice: hopeDice, value: hope }; + configRoll.fear = { dice: fearDice, value: fear }; + configRoll.advantage = { dice: advantageDice, value: roll.dice[2]?.results[0].result ?? null }; + } + if (damage) configRoll.damage = damage; + if (targets) configRoll.targets = targets; + const systemData = + this.type === 'character' && !config.roll.simple ? new DHDualityRoll(configRoll) : configRoll, + cls = getDocumentClass('ChatMessage'), + msg = new cls({ + type: config.chatMessage.type ?? 'dualityRoll', + sound: config.chatMessage.mute ? null : CONFIG.sounds.dice, + system: systemData, + content: config.chatMessage.template, + rolls: [roll] + }); + + await cls.create(msg.toObject()); + } + return roll; + } + + formatRollModifier(roll) { + const modifier = roll.modifier !== null ? Number.parseInt(roll.modifier) : null; + return modifier !== null + ? [ + { + value: modifier, + label: roll.label + ? modifier >= 0 + ? `${roll.label} +${modifier}` + : `${roll.label} ${modifier}` + : null, + title: roll.label + } + ] + : []; } async damageRoll(title, damage, targets, shiftKey) { @@ -300,7 +446,11 @@ export default class DhpActor extends Actor { for (var i = 0; i < rollResult.terms.length; i++) { const term = rollResult.terms[i]; if (term.faces) { - dice.push({ type: `d${term.faces}`, rolls: term.results.map(x => x.result) }); + dice.push({ + type: `d${term.faces}`, + rolls: term.results.map(x => x.result), + total: term.results.reduce((acc, x) => acc + x.result, 0) + }); } else if (term.operator) { } else if (term.number) { const operator = i === 0 ? '' : rollResult.terms[i - 1].operator; @@ -401,11 +551,6 @@ export default class DhpActor extends Actor { } } - async emulateItemDrop(data) { - const event = new DragEvent('drop', { altKey: game.keyboard.isModifierActive('Alt') }); - return this.sheet._onDropItem(event, { data: data }); - } - //Move to action-scope? async useAction(action) { const userTargets = Array.from(game.user.targets); diff --git a/module/documents/item.mjs b/module/documents/item.mjs index 837b5564..54759542 100644 --- a/module/documents/item.mjs +++ b/module/documents/item.mjs @@ -1,25 +1,44 @@ export default class DhpItem extends Item { - _preCreate(data, changes, user) { - super._preCreate(data, changes, user); + /** @inheritdoc */ + getEmbeddedDocument(embeddedName, id, { invalid = false, strict = false } = {}) { + const systemEmbeds = this.system.constructor.metadata.embedded ?? {}; + if (embeddedName in systemEmbeds) { + const path = `system.${systemEmbeds[embeddedName]}`; + return foundry.utils.getProperty(this, path).get(id) ?? null; + } + return super.getEmbeddedDocument(embeddedName, id, { invalid, strict }); } - prepareData() { - super.prepareData(); + /** @inheritDoc */ + prepareEmbeddedDocuments() { + super.prepareEmbeddedDocuments(); + for (const action of this.system.actions ?? []) action.prepareData(); + } - if (this.type === 'class') { - // Bad. Make this better. - // this.system.domains = CONFIG.daggerheart.DOMAIN.classDomainMap[Object.keys(CONFIG.daggerheart.DOMAIN.classDomainMap).find(x => x === this.name.toLowerCase())]; + /** + * @inheritdoc + * @param {object} options - Options which modify the getRollData method. + * @returns + */ + getRollData(options = {}) { + let data; + if (this.system.getRollData) data = this.system.getRollData(options); + else { + const actorRollData = this.actor?.getRollData(options) ?? {}; + data = { ...actorRollData, item: { ...this.system } }; } + + if (data?.item) { + data.item.flags = { ...this.flags }; + data.item.name = this.name; + } + return data; } isInventoryItem() { return ['weapon', 'armor', 'miscellaneous', 'consumable'].includes(this.type); } - _onUpdate(data, options, userId) { - super._onUpdate(data, options, userId); - } - static async createDialog(data = {}, { parent = null, pack = null, ...options } = {}) { const documentName = this.metadata.name; const types = game.documentTypes[documentName].filter(t => t !== CONST.BASE_DOCUMENT_TYPE); @@ -79,4 +98,47 @@ export default class DhpItem extends Item { options }); } + + async selectActionDialog() { + const content = await foundry.applications.handlebars.renderTemplate( + 'systems/daggerheart/templates/views/actionSelect.hbs', + { actions: this.system.actions } + ), + title = 'Select Action', + type = 'div', + data = {}; + return Dialog.prompt({ + title, + // label: title, + content, + type, + callback: html => { + const form = html[0].querySelector('form'), + fd = new foundry.applications.ux.FormDataExtended(form); + return this.system.actions.find(a => a._id === fd.object.actionId); + }, + rejectClose: false + }); + } + + async use(event) { + const actions = this.system.actions; + let response; + if (actions?.length) { + let action = actions[0]; + if (actions.length > 1 && !event?.shiftKey) { + // Actions Choice Dialog + action = await this.selectActionDialog(); + } + if (action) response = action.use(event); + // Check Target + // If action.roll => Roll Dialog + // Else If action.cost => Cost Dialog + // Then + // Apply Cost + // Apply Effect + } + // Display Item Card in chat + return response; + } } diff --git a/module/enrichers/DualityRollEnricher.mjs b/module/enrichers/DualityRollEnricher.mjs index 93c3a999..01fbe1af 100644 --- a/module/enrichers/DualityRollEnricher.mjs +++ b/module/enrichers/DualityRollEnricher.mjs @@ -9,21 +9,26 @@ export function dualityRollEnricher(match, _options) { } export function getDualityMessage(roll) { - const attributeLabel = - roll.attribute && abilities[roll.attribute] + const traitLabel = + roll.trait && abilities[roll.trait] ? game.i18n.format('DAGGERHEART.General.Check', { - check: game.i18n.localize(abilities[roll.attribute].label) + check: game.i18n.localize(abilities[roll.trait].label) }) : null; - const label = attributeLabel ?? game.i18n.localize('DAGGERHEART.General.Duality'); + + const label = traitLabel ?? game.i18n.localize('DAGGERHEART.General.Duality'); + const dataLabel = traitLabel + ? game.i18n.localize(abilities[roll.trait].label) + : game.i18n.localize('DAGGERHEART.General.Duality'); const dualityElement = document.createElement('span'); dualityElement.innerHTML = `