diff --git a/daggerheart.mjs b/daggerheart.mjs index c881801f..a6676f19 100644 --- a/daggerheart.mjs +++ b/daggerheart.mjs @@ -17,6 +17,7 @@ import { socketRegistration } from './module/systemRegistration/_module.mjs'; import { placeables } from './module/canvas/_module.mjs'; +import { registerRollDiceHooks } from './module/dice/dhRoll.mjs'; Hooks.once('init', () => { CONFIG.DH = SYSTEM; @@ -152,6 +153,7 @@ Hooks.on('ready', () => { registerCountdownHooks(); socketRegistration.registerSocketHooks(); registerCountdownApplicationHooks(); + registerRollDiceHooks(); }); Hooks.once('dicesoniceready', () => {}); @@ -209,7 +211,7 @@ Hooks.on('chatMessage', (_, message) => { } const title = traitValue - ? game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilitychecktitle', { + ? game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', { ability: game.i18n.localize(SYSTEM.ACTOR.abilities[traitValue].label) }) : game.i18n.localize('DAGGERHEART.GENERAL.duality'); diff --git a/lang/en.json b/lang/en.json index 4bcb7bdb..c1e7895e 100755 --- a/lang/en.json +++ b/lang/en.json @@ -611,7 +611,7 @@ }, "stress": { "name": "Stress", - "stress": "STR" + "abbreviation": "STR" }, "hope": { "name": "Hope", @@ -619,33 +619,39 @@ }, "armorStack": { "name": "Armor Stack", - "stress": "AS" + "abbreviation": "AS" } }, "Range": { "self": { "name": "Self", - "description": "means yourself." + "description": "means yourself.", + "short": "Self" }, "melee": { "name": "Melee", - "description": "means a character is within touching distance of the target. PCs can generally touch targets up to a few feet away from them, but melee range may be greater for especially large NPCs." + "description": "means a character is within touching distance of the target. PCs can generally touch targets up to a few feet away from them, but melee range may be greater for especially large NPCs.", + "short": "Melee" }, "veryClose": { "name": "Very Close", - "description": "means a distance where one can see fine details of a target- within moments of reaching, if need be. This is usually anywhere from about 5-10 feet away. While in danger, a PC can usually get to anything that’s very close as part of any other action they take. Anything on a battle map that is within the shortest length of a game card (~2-3 inches) can usually be considered very close." + "description": "means a distance where one can see fine details of a target- within moments of reaching, if need be. This is usually anywhere from about 5-10 feet away. While in danger, a PC can usually get to anything that’s very close as part of any other action they take. Anything on a battle map that is within the shortest length of a game card (~2-3 inches) can usually be considered very close.", + "short": "V. Close" }, "close": { "name": "Close", - "description": "means a distance where one can see prominent details of a target-- across a room or to a neighboring market stall, generally about 10-30 feet away. While in danger, a PC can usually get to anything that’s close as part of any other action they take. Anything on a battle map that is within the length of a standard pen or pencil (~5-6 inches) can usually be considered close." + "description": "means a distance where one can see prominent details of a target-- across a room or to a neighboring market stall, generally about 10-30 feet away. While in danger, a PC can usually get to anything that’s close as part of any other action they take. Anything on a battle map that is within the length of a standard pen or pencil (~5-6 inches) can usually be considered close.", + "short": "Close" }, "far": { "name": "Far", - "description": "means a distance where one can see the appearance of a person or object, but probably not in great detail-- across a small battlefield or down a large corridor. This is usually about 30-100 feet away. While under danger, a PC will likely have to make an Agility check to get here safely. Anything on a battle map that is within the length of a standard piece of paper (~10-11 inches) can usually be considered far." + "description": "means a distance where one can see the appearance of a person or object, but probably not in great detail-- across a small battlefield or down a large corridor. This is usually about 30-100 feet away. While under danger, a PC will likely have to make an Agility check to get here safely. Anything on a battle map that is within the length of a standard piece of paper (~10-11 inches) can usually be considered far.", + "short": "Far" }, "veryFar": { "name": "Very Far", - "description": "means a distance where you can see the shape of a person or object, but probably not make outany details-- across a large battlefield or down a long street, generally about 100-300 feet away. While under danger, a PC likely has to make an Agility check to get here safely. Anything on a battle map that is beyond far distance, but still within sight of the characters can usually be considered very far." + "description": "means a distance where you can see the shape of a person or object, but probably not make outany details-- across a large battlefield or down a long street, generally about 100-300 feet away. While under danger, a PC likely has to make an Agility check to get here safely. Anything on a battle map that is beyond far distance, but still within sight of the characters can usually be considered very far.", + "short": "V. Far" } }, "RollTypes": { @@ -1098,6 +1104,7 @@ "range": "Range", "stress": "Stress", "take": "Take", + "target": "Target", "title": "Title", "type": "Type", "unarmored": "Unarmored", diff --git a/module/applications/dialogs/d20RollDialog.mjs b/module/applications/dialogs/d20RollDialog.mjs index 7eaeacb3..db15a7a3 100644 --- a/module/applications/dialogs/d20RollDialog.mjs +++ b/module/applications/dialogs/d20RollDialog.mjs @@ -63,18 +63,6 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio const context = await super._prepareContext(_options); context.rollConfig = this.config; context.hasRoll = !!this.config.roll; - context.roll = this.roll; - context.rollType = this.roll?.constructor.name; - context.experiences = Object.keys(this.config.data.experiences).map(id => ({ - id, - ...this.config.data.experiences[id] - })); - context.selectedExperiences = this.config.experiences; - context.advantage = this.config.roll?.advantage; - context.disadvantage = this.config.roll?.disadvantage; - context.diceOptions = CONFIG.DH.GENERAL.diceTypes; - context.canRoll = true; - context.isLite = this.config.roll?.lite; if (this.config.costs?.length) { const updatedCosts = this.action.calcCosts(this.config.costs); context.costs = updatedCosts; @@ -85,8 +73,22 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio context.uses = this.action.calcUses(this.config.uses); context.canRoll = context.canRoll && this.action.hasUses(context.uses); } - context.extraFormula = this.config.extraFormula; - context.formula = this.roll.constructFormula(this.config); + if(this.roll) { + context.roll = this.roll; + context.rollType = this.roll?.constructor.name; + context.experiences = Object.keys(this.config.data.experiences).map(id => ({ + id, + ...this.config.data.experiences[id] + })); + context.selectedExperiences = this.config.experiences; + context.advantage = this.config.roll?.advantage; + context.disadvantage = this.config.roll?.disadvantage; + context.diceOptions = CONFIG.DH.GENERAL.diceTypes; + context.canRoll = true; + context.isLite = this.config.roll?.lite; + context.extraFormula = this.config.extraFormula; + context.formula = this.roll.constructFormula(this.config); + } return context; } diff --git a/module/applications/sheets-configs/action-config.mjs b/module/applications/sheets-configs/action-config.mjs index c5c70557..0beb8d79 100644 --- a/module/applications/sheets-configs/action-config.mjs +++ b/module/applications/sheets-configs/action-config.mjs @@ -108,7 +108,7 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) { context.config = CONFIG.DH; if (!!this.action.effects) context.effects = this.action.effects.map(e => this.action.item.effects.get(e._id)); if (this.action.damage?.hasOwnProperty('includeBase') && this.action.type === 'attack') - context.hasBaseDamage = !!this.action.parent.damage; + context.hasBaseDamage = !!this.action.parent.attack; context.getRealIndex = this.getRealIndex.bind(this); context.getEffectDetails = this.getEffectDetails.bind(this); context.disableOption = this.disableOption.bind(this); diff --git a/module/applications/sheets/actors/character.mjs b/module/applications/sheets/actors/character.mjs index 63acd31e..66e3c67d 100644 --- a/module/applications/sheets/actors/character.mjs +++ b/module/applications/sheets/actors/character.mjs @@ -494,7 +494,7 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { const config = { event: event, title: `${game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll')}: ${this.actor.name}`, - headerTitle: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilitychecktitle', { + headerTitle: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', { ability: abilityLabel }), roll: { @@ -605,7 +605,7 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { const abilityLabel = game.i18n.localize(abilities[button.dataset.attribute].label); const config = { event: event, - title: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilitychecktitle', { ability: abilityLabel }), + title: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', { ability: abilityLabel }), roll: { trait: button.dataset.attribute } diff --git a/module/applications/sheets/api/base-item.mjs b/module/applications/sheets/api/base-item.mjs index 5e7e8d59..e7d9b54b 100644 --- a/module/applications/sheets/api/base-item.mjs +++ b/module/applications/sheets/api/base-item.mjs @@ -35,7 +35,7 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) { /** @inheritdoc */ static TABS = { primary: { - tabs: [{ id: 'description' }, { id: 'actions' }, { id: 'settings' }], + tabs: [{ id: 'description' }, { id: 'settings' }, { id: 'actions' }], initial: 'description', labelPrefix: 'DAGGERHEART.GENERAL.Tabs' } diff --git a/module/applications/sheets/items/weapon.mjs b/module/applications/sheets/items/weapon.mjs index fdce55fc..d5d09dab 100644 --- a/module/applications/sheets/items/weapon.mjs +++ b/module/applications/sheets/items/weapon.mjs @@ -31,13 +31,12 @@ export default class WeaponSheet extends DHBaseItemSheet { /**@inheritdoc */ async _preparePartContext(partId, context) { super._preparePartContext(partId, context); - switch (partId) { case 'settings': context.features = this.document.system.features.map(x => x.value); + context.systemFields.attack.fields = this.document.system.attack.schema.fields; break; } - return context; } diff --git a/module/applications/ui/chatLog.mjs b/module/applications/ui/chatLog.mjs index 27c53a62..bbf95b17 100644 --- a/module/applications/ui/chatLog.mjs +++ b/module/applications/ui/chatLog.mjs @@ -81,7 +81,9 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo action = actor.system.attack?._id === actionId ? actor.system.attack - : item?.system?.actions?.find(a => a._id === actionId); + : item.system.attack?._id === actionId + ? item.system.attack + : item?.system?.actions?.find(a => a._id === actionId); return action; } @@ -160,7 +162,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo const targetSelection = event.target .closest('.message-content') .querySelector('.button-target-selection.target-selected'), - isHit = Boolean(targetSelection.dataset.targetHit); + isHit = Boolean(targetSelection?.dataset?.targetHit) ?? false; return { isHit, targets: isHit diff --git a/module/applications/ui/combatTracker.mjs b/module/applications/ui/combatTracker.mjs index e5414d47..f9f49ad1 100644 --- a/module/applications/ui/combatTracker.mjs +++ b/module/applications/ui/combatTracker.mjs @@ -65,6 +65,20 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C ]; } + async setCombatantSpotlight(combatantId) { + const combatant = this.viewed.combatants.get(combatantId); + + const toggleTurn = this.viewed.combatants.contents + .sort(this.viewed._sortCombatants) + .map(x => x.id) + .indexOf(combatantId); + + if (this.viewed.turn !== toggleTurn) Hooks.callAll(CONFIG.DH.HOOKS.spotlight, {}); + + await this.viewed.update({ turn: this.viewed.turn === toggleTurn ? null : toggleTurn }); + await combatant.update({ 'system.spotlight.requesting': false }); + } + static async requestSpotlight(_, target) { const { combatantId } = target.closest('[data-combatant-id]')?.dataset ?? {}; const combatant = this.viewed.combatants.get(combatantId); @@ -79,17 +93,7 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C static async toggleSpotlight(_, target) { const { combatantId } = target.closest('[data-combatant-id]')?.dataset ?? {}; - const combatant = this.viewed.combatants.get(combatantId); - - const toggleTurn = this.viewed.combatants.contents - .sort(this.viewed._sortCombatants) - .map(x => x.id) - .indexOf(combatantId); - - if (this.viewed.turn !== toggleTurn) Hooks.callAll(CONFIG.DH.HOOKS.spotlight, {}); - - await this.viewed.update({ turn: this.viewed.turn === toggleTurn ? null : toggleTurn }); - await combatant.update({ 'system.spotlight.requesting': false }); + await this.setCombatantSpotlight(combatantId); } static async setActionTokens(_, target) { diff --git a/module/applications/ui/countdowns.mjs b/module/applications/ui/countdowns.mjs index 895ab908..c229cda1 100644 --- a/module/applications/ui/countdowns.mjs +++ b/module/applications/ui/countdowns.mjs @@ -13,7 +13,7 @@ class Countdowns extends HandlebarsApplicationMixin(ApplicationV2) { } get title() { - return game.i18n.format('DAGGERHEART.APPLICATIONS.Countdown.Title', { + return game.i18n.format('DAGGERHEART.APPLICATIONS.Countdown.title', { type: game.i18n.localize(`DAGGERHEART.APPLICATIONS.Countdown.types.${this.basePath}`) }); } diff --git a/module/applications/ui/fearTracker.mjs b/module/applications/ui/fearTracker.mjs index c2f56c4c..ace2bbb2 100644 --- a/module/applications/ui/fearTracker.mjs +++ b/module/applications/ui/fearTracker.mjs @@ -1,3 +1,5 @@ +import { emitAsGM, GMUpdateEvent, socketEvent } from "../../systemRegistration/socket.mjs"; + const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; /** @@ -96,6 +98,7 @@ export default class FearTracker extends HandlebarsApplicationMixin(ApplicationV } static async increaseFear(event, target) { + if (!game.user.isGM) return; let value = target.dataset.increment ?? 0, operator = value.split('')[0] ?? null; value = Number(value); @@ -103,8 +106,19 @@ export default class FearTracker extends HandlebarsApplicationMixin(ApplicationV } async updateFear(value) { - if (!game.user.isGM) return; + return emitAsGM(GMUpdateEvent.UpdateFear, game.settings.set.bind(game.settings, CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear), value); + /* if(!game.user.isGM) + await game.socket.emit(`system.${CONFIG.DH.id}`, { + action: socketEvent.GMUpdate, + data: { + action: GMUpdateEvent.UpdateFear, + update: value + } + }); + else + game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear, value); */ + /* if (!game.user.isGM) return; value = Math.max(0, Math.min(this.maxFear, value)); - await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear, value); + await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear, value); */ } } diff --git a/module/config/generalConfig.mjs b/module/config/generalConfig.mjs index 1299dccb..64b856a0 100644 --- a/module/config/generalConfig.mjs +++ b/module/config/generalConfig.mjs @@ -58,12 +58,14 @@ export const damageTypes = { physical: { id: 'physical', label: 'DAGGERHEART.CONFIG.DamageType.physical.name', - abbreviation: 'DAGGERHEART.CONFIG.DamageType.physical.abbreviation' + abbreviation: 'DAGGERHEART.CONFIG.DamageType.physical.abbreviation', + icon: ["fa-hand-fist"] }, magical: { id: 'magical', label: 'DAGGERHEART.CONFIG.DamageType.magical.name', - abbreviation: 'DAGGERHEART.CONFIG.DamageType.magical.abbreviation' + abbreviation: 'DAGGERHEART.CONFIG.DamageType.magical.abbreviation', + icon: ["fa-wand-sparkles"] } }; @@ -344,26 +346,26 @@ export const refreshTypes = { }; export const abilityCosts = { + hp: { + id: 'hp', + label: 'DAGGERHEART.CONFIG.HealingType.hitPoints.name', + group: 'Global' + }, + stress: { + id: 'stress', + label: 'DAGGERHEART.CONFIG.HealingType.stress.name', + group: 'Global' + }, hope: { id: 'hope', label: 'Hope', group: 'TYPES.Actor.character' }, - stress: { - id: 'stress', - label: 'DAGGERHEART.CONFIG.HealingType.Stress.Name', - group: 'TYPES.Actor.character' - }, armor: { id: 'armor', label: 'Armor Stack', group: 'TYPES.Actor.character' }, - hp: { - id: 'hp', - label: 'DAGGERHEART.CONFIG.HealingType.HitPoints.Name', - group: 'TYPES.Actor.character' - }, prayer: { id: 'prayer', label: 'Prayer Dice', diff --git a/module/data/action/actionDice.mjs b/module/data/action/actionDice.mjs index cb430d63..8b5bbe40 100644 --- a/module/data/action/actionDice.mjs +++ b/module/data/action/actionDice.mjs @@ -31,7 +31,8 @@ export class DHActionRollData extends foundry.abstract.DataModel { label: 'Should be' }), treshold: new fields.NumberField({ initial: 1, integer: true, min: 1, label: 'Treshold' }) - }) + }), + useDefault: new fields.BooleanField({ initial: false }) }; } @@ -47,7 +48,6 @@ export class DHActionRollData extends foundry.abstract.DataModel { formula = `${multiplier}${this.diceRolling.dice}cs${CONFIG.DH.ACTIONS.diceCompare[this.diceRolling.compare].operator}${this.diceRolling.treshold}`; break; default: - // formula = `${(!!this.parent?.actor?.system?.attack ? `@attackBonus` : `@traits.${this.trait}.total`)}`; formula = ''; break; } diff --git a/module/data/action/attackAction.mjs b/module/data/action/attackAction.mjs index c7cc83ca..9c1c49f8 100644 --- a/module/data/action/attackAction.mjs +++ b/module/data/action/attackAction.mjs @@ -14,9 +14,15 @@ export default class DHAttackAction extends DHDamageAction { prepareData() { super.prepareData(); - if (this.damage.includeBase && !!this.item?.system?.damage) { - const baseDamage = this.getParentDamage(); - this.damage.parts.unshift(new DHDamageData(baseDamage)); + if(!!this.item?.system?.attack) { + if (this.damage.includeBase) { + const baseDamage = this.getParentDamage(); + this.damage.parts.unshift(new DHDamageData(baseDamage)); + } + if(this.roll.useDefault) { + this.roll.trait = this.item.system.attack.roll.trait; + this.roll.type = 'weapon'; + } } } @@ -24,10 +30,10 @@ export default class DHAttackAction extends DHDamageAction { return { value: { multiplier: 'prof', - dice: this.item?.system?.damage.dice, - bonus: this.item?.system?.damage.bonus ?? 0 + dice: this.item?.system?.attack.damage.parts[0].value.dice, + bonus: this.item?.system?.attack.damage.parts[0].value.bonus ?? 0 }, - type: this.item?.system?.damage.type, + type: this.item?.system?.attack.damage.parts[0].type, base: true }; } diff --git a/module/data/action/baseAction.mjs b/module/data/action/baseAction.mjs index 1ea921e9..4d3da21e 100644 --- a/module/data/action/baseAction.mjs +++ b/module/data/action/baseAction.mjs @@ -10,14 +10,10 @@ const fields = foundry.data.fields; /* ToDo - - Add setting and/or checkbox for cost and damage like - Target Check / Target Picker - Range Check - Area of effect and measurement placement - Summon Action create method - - Other - - Auto use action <= Into Roll */ export default class DHBaseAction extends foundry.abstract.DataModel { @@ -160,17 +156,22 @@ export default class DHBaseAction extends foundry.abstract.DataModel { static getSourceConfig(parent) { const updateSource = {}; updateSource.img ??= parent?.img ?? parent?.system?.img; - if (parent?.system?.trait) { - updateSource['roll'] = { - type: this.getRollType(parent), - trait: parent.system.trait - }; - } - if (parent?.type === 'weapon' && !!this.schema.fields.damage) { + if (parent?.type === 'weapon') { updateSource['damage'] = { includeBase: true }; - } - if (parent?.system?.range) { - updateSource['range'] = parent?.system?.range; + updateSource['range'] = parent?.system?.attack?.range; + updateSource['roll'] = { + useDefault: true + } + } else { + if (parent?.system?.trait) { + updateSource['roll'] = { + type: this.getRollType(parent), + trait: parent.system.trait + }; + } + if (parent?.system?.range) { + updateSource['range'] = parent?.system?.range; + } } return updateSource; } @@ -200,28 +201,23 @@ export default class DHBaseAction extends foundry.abstract.DataModel { const isFastForward = event.shiftKey || (!this.hasRoll && !this.hasSave); // Prepare base Config const initConfig = this.initActionConfig(event); - // let config = this.initActionConfig(event); // Prepare Targets const targetConfig = this.prepareTarget(); if (isFastForward && !targetConfig) return ui.notifications.warn('Too many targets selected for that actions.'); - // config = this.prepareTarget(config); // Prepare Range const rangeConfig = this.prepareRange(); - // config = this.prepareRange(config); // Prepare Costs const costsConfig = this.prepareCost(); if (isFastForward && !this.hasCost(costsConfig)) return ui.notifications.warn("You don't have the resources to use that action."); - // config = this.prepareUseCost(config) // Prepare Uses const usesConfig = this.prepareUse(); if (isFastForward && !this.hasUses(usesConfig)) return ui.notifications.warn("That action doesn't have remaining uses."); - // config = this.prepareUseCost(config) // Prepare Roll Data const actorData = this.getRollData(); @@ -238,8 +234,9 @@ export default class DHBaseAction extends foundry.abstract.DataModel { if (Hooks.call(`${CONFIG.DH.id}.preUseAction`, this, config) === false) return; // Display configuration window if necessary - if (config.dialog?.configure && this.requireConfigurationDialog(config)) { - config = await D20RollDialog.configure(config); + // if (config.dialog?.configure && this.requireConfigurationDialog(config)) { + if (this.requireConfigurationDialog(config)) { + config = await D20RollDialog.configure(null, config); if (!config) return; } @@ -250,37 +247,6 @@ export default class DHBaseAction extends foundry.abstract.DataModel { if (!config) return; } - if (this.hasSave) { - /* config.targets.forEach((t) => { - if(t.hit) { - const target = game.canvas.tokens.get(t.id), - actor = target?.actor; - if(!actor) return; - actor.saveRoll({ - event, - title: 'Roll Save', - roll: { - trait: this.save.trait, - difficulty: this.save.difficulty - }, - dialog: { - configure: false - }, - data: actor.getRollData() - }).then(async (result) => { - t.saved = result; - setTimeout(async () => { - const message = ui.chat.collection.get(config.message.id), - msgTargets = message.system.targets, - msgTarget = msgTargets.find(mt => mt.id === t.id); - msgTarget.saved = result; - await message.update({'system.targets': msgTargets}); - },100) - }) - } - }) */ - } - if (this.doFollowUp()) { if (this.rollDamage) await this.rollDamage(event, config); if (this.rollHealing) await this.rollHealing(event, config); @@ -329,12 +295,12 @@ export default class DHBaseAction extends foundry.abstract.DataModel { } prepareTarget() { + if(!this.target?.type) return []; let targets; if (this.target?.type === CONFIG.DH.ACTIONS.targetTypes.self.id) targets = this.constructor.formatTarget(this.actor.token ?? this.actor.prototypeToken); targets = Array.from(game.user.targets); - // foundry.CONST.TOKEN_DISPOSITIONS.FRIENDLY - if (this.target?.type && this.target.type !== CONFIG.DH.ACTIONS.targetTypes.any.id) { + if (this.target.type !== CONFIG.DH.ACTIONS.targetTypes.any.id) { targets = targets.filter(t => this.isTargetFriendly(t)); if (this.target.amount && targets.length > this.target.amount) targets = []; } @@ -540,6 +506,7 @@ export default class DHBaseAction extends foundry.abstract.DataModel { }); }); } + /* SAVE */ async updateChatMessage(message, targetId, changes, chain = true) { setTimeout(async () => { @@ -558,7 +525,6 @@ export default class DHBaseAction extends foundry.abstract.DataModel { }); } } - /* SAVE */ async toChat(origin) { const cls = getDocumentClass('ChatMessage'); diff --git a/module/data/action/effectAction.mjs b/module/data/action/effectAction.mjs index cfe15305..65425a6f 100644 --- a/module/data/action/effectAction.mjs +++ b/module/data/action/effectAction.mjs @@ -3,38 +3,16 @@ import DHBaseAction from './baseAction.mjs'; export default class DHEffectAction extends DHBaseAction { static extraSchemas = ['effects', 'target']; - async use(event, ...args) { - const config = await super.use(event, args); - if (['error', 'warning'].includes(config.type)) return; - return await this.chatApplyEffects(event, config); - } + async trigger(event, data) { + if(this.effects.length) { + const cls = getDocumentClass('ChatMessage'), + msg = { + type: 'applyEffect', + user: game.user.id, + system: data + }; - async chatApplyEffects(event, data) { - const cls = getDocumentClass('ChatMessage'), - systemData = { - title: game.i18n.format('DAGGERHEART.UI.Chat.applyEffect.title', { name: this.name }), - origin: this.actor._id, - description: '', - targets: data.targets.map(x => ({ id: x.id, name: x.name, img: x.img, hit: true })), - action: { - itemId: this.item._id, - actionId: this._id - } - }, - msg = new cls({ - type: 'applyEffect', - user: game.user.id, - system: systemData, - content: await foundry.applications.handlebars.renderTemplate( - 'systems/daggerheart/templates/ui/chat/apply-effects.hbs', - systemData - ) - }); - - cls.create(msg.toObject()); - } - - get chatTemplate() { - return 'systems/daggerheart/templates/ui/chat/apply-effects.hbs'; + return await cls.create(msg); + } else this.toChat(this.id); } } diff --git a/module/data/actor/adversary.mjs b/module/data/actor/adversary.mjs index db7d2ca1..f2f31b6c 100644 --- a/module/data/actor/adversary.mjs +++ b/module/data/actor/adversary.mjs @@ -61,7 +61,9 @@ export default class DhpAdversary extends BaseDataActor { damage: { parts: [ { - multiplier: 'flat' + value: { + multiplier: 'flat' + } } ] } diff --git a/module/data/chat-message/applyEffects.mjs b/module/data/chat-message/applyEffects.mjs index 838dabfb..a7fdda69 100644 --- a/module/data/chat-message/applyEffects.mjs +++ b/module/data/chat-message/applyEffects.mjs @@ -1,11 +1,11 @@ +import DHBaseAction from "../action/baseAction.mjs"; + export default class DHApplyEffect extends foundry.abstract.TypeDataModel { static defineSchema() { const fields = foundry.data.fields; return { title: new fields.StringField(), - origin: new fields.StringField({}), - description: new fields.StringField({}), targets: new fields.ArrayField( new fields.SchemaField({ id: new fields.StringField({ required: true }), @@ -14,10 +14,24 @@ export default class DHApplyEffect extends foundry.abstract.TypeDataModel { hit: new fields.BooleanField({ initial: false }) }) ), - action: new fields.SchemaField({ - itemId: new fields.StringField(), - actionId: new fields.StringField() + targetSelection: new fields.BooleanField({ initial: true }), + source: new fields.SchemaField({ + actor: new fields.StringField(), + item: new fields.StringField(), + action: new fields.StringField() }) }; } + + prepareDerivedData() { + this.hasHitTarget = this.targets.filter(t => t.hit === true).length > 0; + this.currentTargets = + this.targetSelection !== true + ? Array.from(game.user.targets).map(t => DHBaseAction.formatTarget(t)) + : this.targets; + } + + get messageTemplate() { + return 'systems/daggerheart/templates/ui/chat/apply-effects.hbs'; + } } diff --git a/module/data/fields/actionField.mjs b/module/data/fields/actionField.mjs index da520fd1..b83acc2e 100644 --- a/module/data/fields/actionField.mjs +++ b/module/data/fields/actionField.mjs @@ -1,9 +1,6 @@ -import { actionsTypes } from '../action/_module.mjs'; - -// Temporary Solution export default class ActionField extends foundry.data.fields.ObjectField { getModel(value) { - return actionsTypes[value.type] ?? actionsTypes.attack; + return game.system.api.models.actions.actionsTypes[value.type] ?? game.system.api.models.actions.actionsTypes.attack; } /* -------------------------------------------- */ diff --git a/module/data/item/base.mjs b/module/data/item/base.mjs index 1a250327..d0e05eb8 100644 --- a/module/data/item/base.mjs +++ b/module/data/item/base.mjs @@ -42,6 +42,10 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel { return this.parent.actor; } + get actionsList() { + return this.actions; + } + /** * Obtain a data object used to evaluate any dice rolls associated with this Item Type * @param {object} [options] - Options which modify the getRollData method. diff --git a/module/data/item/weapon.mjs b/module/data/item/weapon.mjs index e6cfcbf0..3fb562e0 100644 --- a/module/data/item/weapon.mjs +++ b/module/data/item/weapon.mjs @@ -11,7 +11,7 @@ export default class DHWeapon extends BaseDataItem { hasDescription: true, isQuantifiable: true, isInventoryItem: true, - hasInitialAction: true + // hasInitialAction: true }); } @@ -25,19 +25,8 @@ export default class DHWeapon extends BaseDataItem { //SETTINGS secondary: new fields.BooleanField({ initial: false }), - trait: new fields.StringField({ required: true, choices: CONFIG.DH.ACTOR.abilities, initial: 'agility' }), - range: new fields.StringField({ required: true, choices: CONFIG.DH.GENERAL.range, initial: 'melee' }), burden: new fields.StringField({ required: true, choices: CONFIG.DH.GENERAL.burden, initial: 'oneHanded' }), - //DAMAGE - damage: new fields.SchemaField({ - dice: new fields.StringField({ choices: CONFIG.DH.GENERAL.diceTypes, initial: 'd6' }), - bonus: new fields.NumberField({ nullable: true, initial: null }), - type: new fields.StringField({ - required: true, - choices: CONFIG.DH.GENERAL.damageTypes, - initial: 'physical' - }) - }), + features: new fields.ArrayField( new fields.SchemaField({ value: new fields.StringField({ @@ -49,10 +38,42 @@ export default class DHWeapon extends BaseDataItem { actionIds: new fields.ArrayField(new fields.StringField({ required: true })) }) ), + attack: new ActionField({ + initial: { + name: 'Attack', + img: 'icons/skills/melee/blood-slash-foam-red.webp', + _id: foundry.utils.randomID(), + systemPath: 'attack', + type: 'attack', + range: 'melee', + target: { + type: 'any', + amount: 1 + }, + roll: { + trait: 'agility', + type: 'weapon' + }, + damage: { + parts: [ + { + value: { + multiplier: 'prof', + dice: "d8" + } + } + ] + } + } + }), actions: new fields.ArrayField(new ActionField()) }; } + get actionsList() { + return [this.attack, ...this.actions]; + } + async _preUpdate(changes, options, user) { const allowed = await super._preUpdate(changes, options, user); if (allowed === false) return false; diff --git a/module/data/settings/Automation.mjs b/module/data/settings/Automation.mjs index 013e9e1e..4e375919 100644 --- a/module/data/settings/Automation.mjs +++ b/module/data/settings/Automation.mjs @@ -4,9 +4,21 @@ export default class DhAutomation extends foundry.abstract.DataModel { static defineSchema() { const fields = foundry.data.fields; return { - hope: new fields.BooleanField({ required: true, initial: false }), - actionPoints: new fields.BooleanField({ required: true, initial: false }), - countdowns: new fields.BooleanField({ requireD: true, initial: false }) + hope: new fields.BooleanField({ + required: true, + initial: false, + label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.hope.label' + }), // Label need to be updated into something like "Duality Roll Auto Gain" + a hint + actionPoints: new fields.BooleanField({ + required: true, + initial: false, + label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.actionPoints.label' + }), + countdowns: new fields.BooleanField({ + requireD: true, + initial: false, + label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.countdowns.label' + }) }; } } diff --git a/module/dice/dhRoll.mjs b/module/dice/dhRoll.mjs index c461e14a..c3918a13 100644 --- a/module/dice/dhRoll.mjs +++ b/module/dice/dhRoll.mjs @@ -19,7 +19,7 @@ export default class DHRoll extends Roll { } static async buildConfigure(config = {}, message = {}) { - config.hooks = [...(config.hooks ?? []), '']; + config.hooks = [...this.getHooks(), '']; config.dialog ??= {}; for (const hook of config.hooks) { if (Hooks.call(`${CONFIG.DH.id}.preRoll${hook.capitalize()}`, config, message) === false) return null; @@ -94,6 +94,10 @@ export default class DHRoll extends Roll { config.dialog.configure ??= !(config.event.shiftKey || config.event.altKey || config.event.ctrlKey); } + static getHooks(hooks) { + return hooks ?? []; + } + formatModifier(modifier) { const numTerm = modifier < 0 ? '-' : '+'; return [ @@ -131,3 +135,32 @@ export default class DHRoll extends Roll { return modifierTotal; } } + +export const registerRollDiceHooks = () => { + Hooks.on(`${CONFIG.DH.id}.postRollDuality`, async (config, message) => { + if ( + !game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).hope || + config.roll.type === 'reaction' + ) + return; + + const actor = await fromUuid(config.source.actor), + updates = []; + if (!actor) return; + if (config.roll.isCritical || config.roll.result.duality === 1) updates.push({ type: 'hope', value: 1 }); + if (config.roll.isCritical) updates.push({ type: 'stress', value: -1 }); + if (config.roll.result.duality === -1) updates.push({ type: 'fear', value: 1 }); + + if (updates.length) actor.modifyResource(updates); + + if (!config.roll.hasOwnProperty('success') && !config.targets.length) return; + + const rollResult = config.roll.success || config.targets.some(t => t.hit), + looseSpotlight = !rollResult || config.roll.result.duality === -1; + + if (looseSpotlight && game.combat?.active) { + const currentCombatant = game.combat.combatants.get(game.combat.current?.combatantId); + if (currentCombatant?.actorId == actor.id) ui.combat.setCombatantSpotlight(currentCombatant.id); + } + }); +}; diff --git a/module/dice/dualityRoll.mjs b/module/dice/dualityRoll.mjs index 0f1c51d9..4d0e99a3 100644 --- a/module/dice/dualityRoll.mjs +++ b/module/dice/dualityRoll.mjs @@ -79,7 +79,10 @@ export default class DualityRoll extends D20Roll { return game.i18n.localize(label); } - updateFormula() {} + static getHooks(hooks) { + + return [...(hooks ?? []), 'Duality']; + } createBaseDice() { if ( diff --git a/module/documents/item.mjs b/module/documents/item.mjs index be6e004b..a8a4a6d8 100644 --- a/module/documents/item.mjs +++ b/module/documents/item.mjs @@ -77,10 +77,10 @@ export default class DHItem extends foundry.documents.Item { }); } - async selectActionDialog() { + async selectActionDialog(prevEvent) { const content = await foundry.applications.handlebars.renderTemplate( 'systems/daggerheart/templates/dialogs/actionSelect.hbs', - { actions: this.system.actions } + { actions: this.system.actionsList } ), title = 'Select Action'; @@ -89,19 +89,23 @@ export default class DHItem extends foundry.documents.Item { content, ok: { label: title, - callback: (event, button, dialog) => - this.system.actions.find(a => a._id === button.form.elements.actionId.value) + callback: (event, button, dialog) => { + Object.defineProperty(prevEvent, "shiftKey", { + get() { return event.shiftKey; }, + }); + return this.system.actionsList.find(a => a._id === button.form.elements.actionId.value) + } } }); } async use(event) { - const actions = this.system.actions; + const actions = this.system.actionsList; if (actions?.length) { let action = actions[0]; if (actions.length > 1 && !event?.shiftKey) { // Actions Choice Dialog - action = await this.selectActionDialog(); + action = await this.selectActionDialog(event); } if (action) return action.use(event); } diff --git a/module/systemRegistration/socket.mjs b/module/systemRegistration/socket.mjs index 0be0a633..d7f79df9 100644 --- a/module/systemRegistration/socket.mjs +++ b/module/systemRegistration/socket.mjs @@ -39,20 +39,16 @@ export const registerSocketHooks = () => { } break; case GMUpdateEvent.UpdateSetting: - if (game.user.isGM) { - await game.settings.set(CONFIG.DH.id, data.uuid, data.update); - } + await game.settings.set(CONFIG.DH.id, data.uuid, data.update); break; case GMUpdateEvent.UpdateFear: - if (game.user.isGM) { - await game.settings.set( - CONFIG.DH.id, - CONFIG.DH.SETTINGS.gameSettings.Resources.Fear, - Math.max(Math.min(data.update, 6), 0) - ); - Hooks.callAll(socketEvent.DhpFearUpdate); - await game.socket.emit(`system.${CONFIG.DH.id}`, { action: socketEvent.DhpFearUpdate }); - } + await game.settings.set( + CONFIG.DH.id, + CONFIG.DH.SETTINGS.gameSettings.Resources.Fear, + Math.max(0, Math.min(game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).maxFear, data.update)) + ); + /* Hooks.callAll(socketEvent.DhpFearUpdate); + await game.socket.emit(`system.${CONFIG.DH.id}`, { action: socketEvent.DhpFearUpdate }); */ break; } @@ -66,3 +62,22 @@ export const registerSocketHooks = () => { } }); }; + +export const emitAsGM = async (eventName, callback, args) => { + if(!game.user.isGM) { + return new Promise(async (resolve, reject) => { + try { + const response = await game.socket.emit(`system.${CONFIG.DH.id}`, { + action: socketEvent.GMUpdate, + data: { + action: eventName, + update: args + } + }); + resolve(response); + } catch (error) { + reject(error); + } + }) + } else return callback(args); +} diff --git a/styles/daggerheart.css b/styles/daggerheart.css index f15583d2..b64cd424 100755 --- a/styles/daggerheart.css +++ b/styles/daggerheart.css @@ -5776,10 +5776,14 @@ body.theme-light.application.daggerheart.dialog { outline: 2px solid light-dark(#222, #efe6d8); } .application.dh-style fieldset[disabled], +.application.dh-style fieldset.child-disabled .form-group, .application.dh-style fieldset select[disabled], .application.dh-style fieldset input[disabled] { opacity: 0.5; } +.application.dh-style fieldset.child-disabled .form-group { + pointer-events: none; +} .application.dh-style fieldset .nest-inputs { display: flex; align-items: center; @@ -6384,6 +6388,7 @@ body.theme-light.application.daggerheart.dialog { justify-content: center; align-items: center; font-size: 12px; + gap: 4px; } .application.daggerheart.dh-style .inventory-item .controls { display: flex; diff --git a/styles/less/global/elements.less b/styles/less/global/elements.less index 2adc28c4..03772bfb 100755 --- a/styles/less/global/elements.less +++ b/styles/less/global/elements.less @@ -214,11 +214,16 @@ } &[disabled], + &.child-disabled .form-group, select[disabled], input[disabled] { opacity: 0.5; } + &.child-disabled .form-group { + pointer-events: none; + } + .nest-inputs { display: flex; align-items: center; diff --git a/styles/less/global/inventory-item.less b/styles/less/global/inventory-item.less index 8d521c82..24e53165 100644 --- a/styles/less/global/inventory-item.less +++ b/styles/less/global/inventory-item.less @@ -53,6 +53,7 @@ justify-content: center; align-items: center; font-size: 12px; + gap: 4px; } } } diff --git a/templates/actionTypes/damage.hbs b/templates/actionTypes/damage.hbs index 26964a4a..59519e6d 100644 --- a/templates/actionTypes/damage.hbs +++ b/templates/actionTypes/damage.hbs @@ -4,20 +4,20 @@ Damage {{#unless (eq path 'system.attack.')}}{{/unless}} - {{#unless @root.isNPC}} + {{#unless (or @root.isNPC path)}} {{#if @root.hasBaseDamage}} {{formField @root.fields.damage.fields.includeBase value=@root.source.damage.includeBase label="Include Item Damage" name="damage.includeBase" classes="checkbox" }} {{/if}} {{/unless}} {{#each source.parts as |dmg index|}} - {{#if @root.isNPC}} + {{#if (or @root.isNPC ../path)}} {{formField ../fields.value.fields.custom.fields.enabled value=dmg.value.custom.enabled name=(concat ../path "damage.parts." index ".value.custom.enabled") classes="checkbox"}} {{#if dmg.value.custom.enabled}} {{formField ../fields.value.fields.custom.fields.formula value=dmg.value.custom.formula name=(concat ../path "damage.parts." index ".value.custom.formula") localize=true}} {{else}}
- {{formField ../fields.value.fields.flatMultiplier value=dmg.value.flatMultiplier name=(concat ../path "damage.parts." index ".value.flatMultiplier") label="Multiplier" classes="inline-child" }} + {{#if @root.isNPC}}{{formField ../fields.value.fields.flatMultiplier value=dmg.value.flatMultiplier name=(concat ../path "damage.parts." index ".value.flatMultiplier") label="Multiplier" classes="inline-child" }}{{/if}} {{formField ../fields.value.fields.dice value=dmg.value.dice name=(concat ../path "damage.parts." index ".value.dice") classes="inline-child"}} {{formField ../fields.value.fields.bonus value=dmg.value.bonus name=(concat ../path "damage.parts." index ".value.bonus") localize=true classes="inline-child"}}
diff --git a/templates/actionTypes/part/damage-part.hbs b/templates/actionTypes/part/damage-part.hbs new file mode 100644 index 00000000..e69de29b diff --git a/templates/actionTypes/roll.hbs b/templates/actionTypes/roll.hbs index 6d56162a..874d09f2 100644 --- a/templates/actionTypes/roll.hbs +++ b/templates/actionTypes/roll.hbs @@ -1,5 +1,8 @@ -
- Roll +
+ + Roll + {{#if @root.hasBaseDamage}}{{formInput fields.useDefault name="roll.useDefault" value=source.useDefault dataset=(object tooltip="Use default Item values" tooltipDirection="UP")}}{{/if}} + {{#if @root.isNPC}} {{formField fields.bonus label="Bonus" name="roll.bonus" value=source.bonus}} {{formField fields.advState label= "Advantage State" name="roll.advState" value=source.advState localize=true}} diff --git a/templates/dialogs/dice-roll/rollSelection.hbs b/templates/dialogs/dice-roll/rollSelection.hbs index c01b33ba..5c0ba41d 100644 --- a/templates/dialogs/dice-roll/rollSelection.hbs +++ b/templates/dialogs/dice-roll/rollSelection.hbs @@ -1,6 +1,6 @@
- {{#if @root.hasRoll}}
+ {{#if @root.hasRoll}} {{#unless @root.isLite}}
{{#if (eq @root.rollType 'D20Roll')}} @@ -121,6 +121,10 @@ Roll -
+ {{else}} + {{/if}} +
\ No newline at end of file diff --git a/templates/settings/automation-settings.hbs b/templates/settings/automation-settings.hbs index 1b7b9e44..7a637d08 100644 --- a/templates/settings/automation-settings.hbs +++ b/templates/settings/automation-settings.hbs @@ -1,22 +1,7 @@
-
- -
- {{formInput settingFields.schema.fields.hope value=settingFields._source.hope }} -
-
-
- -
- {{formInput settingFields.schema.fields.actionPoints value=settingFields._source.actionPoints }} -
-
-
- -
- {{formInput settingFields.schema.fields.countdowns value=settingFields._source.countdowns }} -
-
+ {{formGroup settingFields.schema.fields.hope value=settingFields._source.hope localize=true}} + {{formGroup settingFields.schema.fields.actionPoints value=settingFields._source.actionPoints localize=true}} + {{formGroup settingFields.schema.fields.countdowns value=settingFields._source.countdowns localize=true}}
- {{localize "DAGGERHEART.GENERAL.title"}} - {{localize "DAGGERHEART.GENERAL.Dice.single"}} - {{formGroup systemFields.damage.fields.dice value=source.system.damage.dice}} - {{localize "DAGGERHEART.GENERAL.bonus"}} - {{formGroup systemFields.damage.fields.bonus value=source.system.damage.bonus}} - {{localize "DAGGERHEART.GENERAL.type"}} - {{formGroup systemFields.damage.fields.type value=source.system.damage.type localize=true}} + {{#with systemFields.attack.fields.damage.fields.parts.element.fields as | fields | }} + {{#with (lookup ../document.system.attack.damage.parts 0) as | source | }} + {{localize "DAGGERHEART.GENERAL.title"}} + {{localize "DAGGERHEART.GENERAL.Dice.single"}} + {{formInput fields.value.fields.dice value=source.value.dice name="system.attack.damage.parts.0.value.dice"}} + {{localize "DAGGERHEART.GENERAL.bonus"}} + {{formInput fields.value.fields.bonus value=source.value.bonus name="system.attack.damage.parts.0.value.bonus" localize=true}} + {{localize "DAGGERHEART.GENERAL.type"}} + {{formInput fields.type value=source.type name="system.attack.damage.parts.0.type" localize=true}} + + {{/with}} + {{/with}}
{{localize "TYPES.Item.feature"}} diff --git a/templates/ui/chat/apply-effects.hbs b/templates/ui/chat/apply-effects.hbs index 41e31888..ecb51134 100644 --- a/templates/ui/chat/apply-effects.hbs +++ b/templates/ui/chat/apply-effects.hbs @@ -1,6 +1,8 @@
{{title}}
{{{description}}}
+
+
diff --git a/templates/ui/chat/damage-roll.hbs b/templates/ui/chat/damage-roll.hbs index f1fbb6c1..1d1a8c93 100644 --- a/templates/ui/chat/damage-roll.hbs +++ b/templates/ui/chat/damage-roll.hbs @@ -4,7 +4,7 @@
- +
\ No newline at end of file diff --git a/templates/ui/combatTracker/combatTracker.hbs b/templates/ui/combatTracker/combatTracker.hbs index 9b5b2a4f..8ad4f7d5 100644 --- a/templates/ui/combatTracker/combatTracker.hbs +++ b/templates/ui/combatTracker/combatTracker.hbs @@ -1,8 +1,8 @@
{{#if (gt this.characters.length 0)}} - {{> 'systems/daggerheart/templates/ui/combat/combatTrackerSection.hbs' this title=(localize "DAGGERHEART.GENERAL.Character.plural") turns=this.characters}} + {{> 'systems/daggerheart/templates/ui/combatTracker/combatTrackerSection.hbs' this title=(localize "DAGGERHEART.GENERAL.Character.plural") turns=this.characters}} {{/if}} {{#if (gt this.adversaries.length 0)}} - {{> 'systems/daggerheart/templates/ui/combat/combatTrackerSection.hbs' this title=(localize "DAGGERHEART.GENERAL.Adversary.plural") turns=this.adversaries}} + {{> 'systems/daggerheart/templates/ui/combatTracker/combatTrackerSection.hbs' this title=(localize "DAGGERHEART.GENERAL.Adversary.plural") turns=this.adversaries}} {{/if}}
\ No newline at end of file diff --git a/templates/ui/combatTracker/combatTrackerHeader.hbs b/templates/ui/combatTracker/combatTrackerHeader.hbs index 4eb68c9a..de666168 100644 --- a/templates/ui/combatTracker/combatTrackerHeader.hbs +++ b/templates/ui/combatTracker/combatTrackerHeader.hbs @@ -60,7 +60,7 @@
{{fear}}
- + {{/if}}