diff --git a/daggerheart.mjs b/daggerheart.mjs index 48f4a615..06f1aaf6 100644 --- a/daggerheart.mjs +++ b/daggerheart.mjs @@ -8,8 +8,9 @@ import * as fields from './module/data/fields/_module.mjs'; import RegisterHandlebarsHelpers from './module/helpers/handlebarsHelper.mjs'; import { enricherConfig, enricherRenderSetup } from './module/enrichers/_module.mjs'; import { getCommandTarget, rollCommandToJSON } from './module/helpers/utils.mjs'; -import { BaseRoll, DHRoll, DualityRoll, D20Roll, DamageRoll } from './module/dice/_module.mjs'; +import { BaseRoll, DHRoll, DualityRoll, D20Roll, DamageRoll, FateRoll } from './module/dice/_module.mjs'; import { enrichedDualityRoll } from './module/enrichers/DualityRollEnricher.mjs'; +import { enrichedFateRoll, getFateType } from './module/enrichers/FateRollEnricher.mjs'; import { handlebarsRegistration, runMigrations, @@ -23,12 +24,13 @@ import TemplateManager from './module/documents/templateManager.mjs'; CONFIG.DH = SYSTEM; CONFIG.TextEditor.enrichers.push(...enricherConfig); -CONFIG.Dice.rolls = [BaseRoll, DHRoll, DualityRoll, D20Roll, DamageRoll]; +CONFIG.Dice.rolls = [BaseRoll, DHRoll, DualityRoll, D20Roll, DamageRoll, FateRoll]; CONFIG.Dice.daggerheart = { DHRoll: DHRoll, DualityRoll: DualityRoll, D20Roll: D20Roll, - DamageRoll: DamageRoll + DamageRoll: DamageRoll, + FateRoll: FateRoll }; CONFIG.Actor.documentClass = documents.DhpActor; @@ -308,6 +310,38 @@ Hooks.on('chatMessage', (_, message) => { }); return false; } + + if (message.startsWith('/fr')) { + const result = + message.trim().toLowerCase() === '/fr' ? { result: {} } : rollCommandToJSON(message.replace(/\/fr\s?/, '')); + if (!result) { + ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.fateParsing')); + return false; + } + + const { result: rollCommand, flavor } = result; + + const fateTypeFromRollCommand = getFateType(rollCommand?.type); + + if (fateTypeFromRollCommand == "BAD") { + ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.fateTypeParsing')); + return false; + } + + const fateType = fateTypeFromRollCommand; + + const target = getCommandTarget({ allowNull: true }); + const title = fateType + ' Fate Roll'; + + enrichedFateRoll({ + target, + title, + label: 'test', + fateType + }); + return false; + } + }); const updateActorsRangeDependentEffects = async token => { diff --git a/lang/en.json b/lang/en.json index a78ed588..c5a503c6 100755 --- a/lang/en.json +++ b/lang/en.json @@ -1001,15 +1001,15 @@ "DeathMoves": { "avoidDeath": { "name": "Avoid Death", - "description": "You drop unconscious temporarily and work with the GM to describe how the situation gets much worse because of it. Then roll your Fear die; if its value is equal to or under your Level, take a Scar." + "description": "Your character avoids death and faces the consequences. They temporarily drop unconscious, and then you work with the GM to describe how the situation worsens. While unconscious, your character can't move or act, and they can't be targeted by an attack. They return to consciousness when an ally clears 1 or more of their marked Hit Points or when the party finishes a long rest. After your character falls unconscious, roll your Hope Die. If its value is equal to or less than your character's level, they gain a scar: permanently cross out a Hope slot and work with the GM to determine its lasting narrative impact and how, if possible, it can be restored. If you ever cross out your last Hope slot, your character's journey ends." }, "riskItAll": { "name": "Risk It All", - "description": "Roll your Duality Dice. If Hope is higher, you stay on your feet and clear an amount of Hit Points and/or Stress equal to the value of the Hope die (divide the Hope die value up between these however you’d prefer). If your Fear die is higher, you cross through the veil of death. If the Duality Dice are tied, you stay on your feet and clear all Hit Points and Stress." + "description": "Roll your Duality Dice. If the Hope Die is higher, your character stays on their feet and clears a number of Hit Points or Stress equal to the value of the Hope Die (you can divide the Hope Die value between Hit Points and Stress however you'd prefer). If the Fear Die is higher, your character crosses through the veil of death. If the Duality Dice show matching results, your character stays up and clears all Hit Points and Stress." }, "blazeOfGlory": { "name": "Blaze Of Glory", - "description": "With Blaze of Glory, the player is accepting death for the character. Take one action (at GM discretion), which becomes an automatic critical success, then cross through the veil of death." + "description": " Your character embraces death and goes out in a blaze of glory. Take one final action. It automatically critically succeeds (with GM approval), and then you cross through the veil of death. NOTE: A Blaze of Glory effect has been added to your character. Any Duality Roll will automatically be a critical." } }, "DomainCardTypes": { @@ -2109,11 +2109,14 @@ "plural": "Experiences" }, "failure": "Failure", + "fate": "Fate", + "fateRoll": "Fate Roll", "fear": "Fear", "features": "Features", "formula": "Formula", "general": "General", "gm": "GM", + "guaranteedCriticalSuccess": "Guaranteed Critical Success", "healing": "Healing", "healingRoll": "Healing Roll", "hit": { @@ -2173,6 +2176,7 @@ "rollWith": "{roll} Roll", "save": "Save", "scalable": "Scalable", + "scars": "Scars", "situationalBonus": "Situational Bonus", "spent": "Spent", "step": "Step", @@ -2720,7 +2724,9 @@ "noAssignedPlayerCharacter": "You have no assigned character.", "noSelectedToken": "You have no selected token", "onlyUseableByPC": "This can only be used with a PC token", - "dualityParsing": "Duality roll not properly formated", + "dualityParsing": "Duality roll not properly formatted", + "fateParsing": "Fate roll not properly formatted", + "fateTypeParsing": "Fate roll not properly formatted, bad fate type. Valid types are 'Hope' and 'Fear'", "attributeFaulty": "The supplied Attribute doesn't exist", "domainCardWrongDomain": "You don't have access to that Domain", "domainCardToHighLevel": "The Domain Card is too high level to be selected", diff --git a/module/applications/dialogs/deathMove.mjs b/module/applications/dialogs/deathMove.mjs index d0686d2b..1bd9d242 100644 --- a/module/applications/dialogs/deathMove.mjs +++ b/module/applications/dialogs/deathMove.mjs @@ -1,6 +1,10 @@ +import { enrichedFateRoll } from '../../enrichers/FateRollEnricher.mjs'; +import { enrichedDualityRoll } from '../../enrichers/DualityRollEnricher.mjs'; + + const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; -export default class DhpDeathMove extends HandlebarsApplicationMixin(ApplicationV2) { +export default class DhDeathMove extends HandlebarsApplicationMixin(ApplicationV2) { constructor(actor) { super({}); @@ -38,6 +42,99 @@ export default class DhpDeathMove extends HandlebarsApplicationMixin(Application return context; } + async handleAvoidDeath() { + const target = this.actor.uuid; + const config = await enrichedFateRoll({ + target, + title: "Avoid Death Hope Fate Roll", + label: 'test', + fateType: "Hope" + }); + if (config.roll.fate.value <= this.actor.system.levelData.level.current) { + // apply scarring - for now directly apply - later add a button. + const newScarAmount = this.actor.system.scars + 1; + + await this.actor.update( + { + system: { + scars: newScarAmount + } + } + ); + } + } + + async clearAllStressAndHitpoints() { + await this.actor.update( + { + system: { + resources: { + hitPoints: { + value: 0 + }, + stress: { + value: 0 + } + } + } + } + ); + } + + async handleRiskItAll() { + const config = await enrichedDualityRoll({ + reaction: true, + traitValue: null, + target: null, + difficulty: null, + title: "Risk It All", + label: 'test', + actionType: null, + advantage: null + }); + + if (config.roll.isCritical) { + console.log("Clear all stress and HP"); + this.clearAllStressAndHitpoints(); + return; + } + + // Hope + if (config.roll.result.duality == 1) { + console.log("Need to clear up Stress and HP up to hope value"); + console.log("Hope rolled", config.roll.hope.value); + if (config.roll.hope.value >= (this.actor.system.resources.hitPoints.value + this.actor.system.resources.stress.value)) { + console.log("Hope roll value is more than the HP + Stress, auto- remove"); + this.clearAllStressAndHitpoints(); + } + return; + } + + //Fear + if (config.roll.result.duality == -1) { + console.log("You have died..."); + return; + } + + } + + async handleBlazeOfGlory() { + this.actor.createEmbeddedDocuments('ActiveEffect', [ + { + name: game.i18n.localize('DAGGERHEART.CONFIG.DeathMoves.blazeOfGlory.name'), + description: game.i18n.localize('DAGGERHEART.CONFIG.DeathMoves.blazeOfGlory.description'), + img: CONFIG.DH.GENERAL.deathMoves.blazeOfGlory.img, + changes: [ + { + key: 'system.rules.roll.guaranteedCritical', + mode: 2, + value: "true" + } + ] + } + ]); + } + static selectMove(_, button) { const move = button.dataset.move; this.selectedMove = CONFIG.DH.GENERAL.deathMoves[move]; @@ -46,6 +143,10 @@ export default class DhpDeathMove extends HandlebarsApplicationMixin(Application } static async takeMove() { + const autoExpandDescription = game.settings.get( + CONFIG.DH.id, + CONFIG.DH.SETTINGS.gameSettings.appearance + ).expandRollMessage?.desc; const cls = getDocumentClass('ChatMessage'); const msg = { user: game.user.id, @@ -57,7 +158,9 @@ export default class DhpDeathMove extends HandlebarsApplicationMixin(Application author: game.users.get(game.user.id), title: game.i18n.localize(this.selectedMove.name), img: this.selectedMove.img, - description: game.i18n.localize(this.selectedMove.description) + description: game.i18n.localize(this.selectedMove.description), + open: autoExpandDescription ? 'open' : '', + chevron: autoExpandDescription ? 'fa-chevron-up' : 'fa-chevron-down' } ), title: game.i18n.localize( @@ -74,5 +177,21 @@ export default class DhpDeathMove extends HandlebarsApplicationMixin(Application cls.create(msg); this.close(); + + if (CONFIG.DH.GENERAL.deathMoves.avoidDeath === this.selectedMove) { + this.handleAvoidDeath(); + return; + } + + if (CONFIG.DH.GENERAL.deathMoves.riskItAll === this.selectedMove) { + this.handleRiskItAll(); + return; + } + + if (CONFIG.DH.GENERAL.deathMoves.blazeOfGlory === this.selectedMove) { + this.handleBlazeOfGlory(); + return; + } + } } diff --git a/module/applications/sheets/actors/character.mjs b/module/applications/sheets/actors/character.mjs index 594269be..852757a6 100644 --- a/module/applications/sheets/actors/character.mjs +++ b/module/applications/sheets/actors/character.mjs @@ -1,5 +1,5 @@ import DHBaseActorSheet from '../api/base-actor.mjs'; -import DhpDeathMove from '../../dialogs/deathMove.mjs'; +import DhDeathMove from '../../dialogs/deathMove.mjs'; import { abilities } from '../../../config/actorConfig.mjs'; import { CharacterLevelup, LevelupViewMode } from '../../levelup/_module.mjs'; import DhCharacterCreation from '../../characterCreation/characterCreation.mjs'; @@ -666,7 +666,7 @@ export default class CharacterSheet extends DHBaseActorSheet { * @type {ApplicationClickAction} */ static async #makeDeathMove() { - await new DhpDeathMove(this.document).render({ force: true }); + await new DhDeathMove(this.document).render({ force: true }); } /** diff --git a/module/data/actor/character.mjs b/module/data/actor/character.mjs index eba46f10..6885dc84 100644 --- a/module/data/actor/character.mjs +++ b/module/data/actor/character.mjs @@ -78,12 +78,7 @@ export default class DhCharacter extends BaseDataActor { bags: new fields.NumberField({ initial: 0, integer: true }), chests: new fields.NumberField({ initial: 0, integer: true }) }), - scars: new fields.TypedObjectField( - new fields.SchemaField({ - name: new fields.StringField({}), - description: new fields.StringField() - }) - ), + scars: new fields.NumberField({ initial: 0, integer: true, label: 'DAGGERHEART.GENERAL.scars' }), biography: new fields.SchemaField({ background: new fields.HTMLField(), connections: new fields.HTMLField(), @@ -283,6 +278,9 @@ export default class DhCharacter extends BaseDataActor { runeWard: new fields.BooleanField({ initial: false }), burden: new fields.SchemaField({ ignore: new fields.BooleanField() + }), + roll: new fields.SchemaField({ + guaranteedCritical: new fields.BooleanField() }) }) }; @@ -624,7 +622,7 @@ export default class DhCharacter extends BaseDataActor { ? armor.system.baseThresholds.severe + this.levelData.level.current : this.levelData.level.current * 2 }; - this.resources.hope.max -= Object.keys(this.scars).length; + this.resources.hope.max -= this.scars; this.resources.hitPoints.max += this.class.value?.system?.hitPoints ?? 0; } @@ -696,4 +694,9 @@ export default class DhCharacter extends BaseDataActor { t => !!t ); } + + static migrateData(source) { + if (typeof (source.scars) === 'object') source.scars = 0; + return super.migrateData(source); + } } diff --git a/module/data/chat-message/_modules.mjs b/module/data/chat-message/_modules.mjs index ec095aac..c671de31 100644 --- a/module/data/chat-message/_modules.mjs +++ b/module/data/chat-message/_modules.mjs @@ -8,6 +8,7 @@ export const config = { adversaryRoll: DHActorRoll, damageRoll: DHActorRoll, dualityRoll: DHActorRoll, + fateRoll: DHActorRoll, groupRoll: DHGroupRoll, systemMessage: DHSystemMessage }; diff --git a/module/dice/_module.mjs b/module/dice/_module.mjs index e6755a74..b9339d87 100644 --- a/module/dice/_module.mjs +++ b/module/dice/_module.mjs @@ -3,3 +3,4 @@ export { default as D20Roll } from './d20Roll.mjs'; export { default as DamageRoll } from './damageRoll.mjs'; export { default as DHRoll } from './dhRoll.mjs'; export { default as DualityRoll } from './dualityRoll.mjs'; +export { default as FateRoll } from './fateRoll.mjs'; diff --git a/module/dice/dualityRoll.mjs b/module/dice/dualityRoll.mjs index d2e20213..1481c546 100644 --- a/module/dice/dualityRoll.mjs +++ b/module/dice/dualityRoll.mjs @@ -12,6 +12,7 @@ export default class DualityRoll extends D20Roll { constructor(formula, data = {}, options = {}) { super(formula, data, options); this.rallyChoices = this.setRallyChoices(); + this.guaranteedCritical = options.guaranteedCritical; } static messageType = 'dualityRoll'; @@ -25,29 +26,23 @@ export default class DualityRoll extends D20Roll { } get dHope() { - // if ( !(this.terms[0] instanceof foundry.dice.terms.Die) ) return; if (!(this.dice[0] instanceof foundry.dice.terms.Die)) this.createBaseDice(); return this.dice[0]; - // return this.#hopeDice; } set dHope(faces) { if (!(this.dice[0] instanceof foundry.dice.terms.Die)) this.createBaseDice(); - this.terms[0].faces = this.getFaces(faces); - // this.#hopeDice = `d${face}`; + this.dice[0].faces = this.getFaces(faces); } get dFear() { - // if ( !(this.terms[1] instanceof foundry.dice.terms.Die) ) return; if (!(this.dice[1] instanceof foundry.dice.terms.Die)) this.createBaseDice(); return this.dice[1]; - // return this.#fearDice; } set dFear(faces) { if (!(this.dice[1] instanceof foundry.dice.terms.Die)) this.createBaseDice(); this.dice[1].faces = this.getFaces(faces); - // this.#fearDice = `d${face}`; } get dAdvantage() { @@ -90,26 +85,27 @@ export default class DualityRoll extends D20Roll { } get isCritical() { + if (this.guaranteedCritical) { + return true; + } if (!this.dHope._evaluated || !this.dFear._evaluated) return; return this.dHope.total === this.dFear.total; } get withHope() { - if (!this._evaluated) return; + if (!this._evaluated || this.guaranteedCritical) return; return this.dHope.total > this.dFear.total; } get withFear() { - if (!this._evaluated) return; + if (!this._evaluated || this.guaranteedCritical) return; return this.dHope.total < this.dFear.total; } get totalLabel() { - const label = this.withHope - ? 'DAGGERHEART.GENERAL.hope' - : this.withFear - ? 'DAGGERHEART.GENERAL.fear' - : 'DAGGERHEART.GENERAL.criticalSuccess'; + const label = this.guaranteedCritical ? 'DAGGERHEART.GENERAL.guaranteedCriticalSuccess' : + this.isCritical ? 'DAGGERHEART.GENERAL.criticalSuccess' : + this.withHope ? 'DAGGERHEART.GENERAL.hope' : 'DAGGERHEART.GENERAL.fear'; return game.i18n.localize(label); } @@ -120,6 +116,9 @@ export default class DualityRoll extends D20Roll { /** @inheritDoc */ static fromData(data) { + if (data.options.guaranteedCritical) { + console.log("TODO: set the max values for Hope and Fear here?"); + } data.terms[0].class = foundry.dice.terms.Die.name; data.terms[2].class = foundry.dice.terms.Die.name; return super.fromData(data); @@ -173,6 +172,23 @@ export default class DualityRoll extends D20Roll { return modifiers; } + static async buildConfigure(config = {}, message = {}) { + console.log("buildConfigure, config", config); + config.dialog ??= {}; + config.guaranteedCritical = config.data?.parent?.appliedEffects.reduce((a, c) => { + const change = c.changes.find(ch => ch.key === 'system.rules.roll.guaranteedCritical'); + if (change) a = true; + return a; + }, false); + + if (config.guaranteedCritical) { + config.dialog.configure = false; + } + + return super.buildConfigure(config, message); + } + + static async buildEvaluate(roll, config = {}, message = {}) { await super.buildEvaluate(roll, config, message); @@ -190,7 +206,7 @@ export default class DualityRoll extends D20Roll { data.hope = { dice: roll.dHope.denomination, - value: roll.dHope.total, + value: this.guaranteedCritical ? 0 : roll.dHope.total, rerolled: { any: roll.dHope.results.some(x => x.rerolled), rerolls: roll.dHope.results.filter(x => x.rerolled) @@ -198,7 +214,7 @@ export default class DualityRoll extends D20Roll { }; data.fear = { dice: roll.dFear.denomination, - value: roll.dFear.total, + value: this.guaranteedCritical ? 0 : roll.dFear.total, rerolled: { any: roll.dFear.results.some(x => x.rerolled), rerolls: roll.dFear.results.filter(x => x.rerolled) @@ -210,7 +226,7 @@ export default class DualityRoll extends D20Roll { }; data.result = { duality: roll.withHope ? 1 : roll.withFear ? -1 : 0, - total: roll.dHope.total + roll.dFear.total, + total: this.guaranteedCritical ? 0 : roll.dHope.total + roll.dFear.total, label: roll.totalLabel }; diff --git a/module/dice/fateRoll.mjs b/module/dice/fateRoll.mjs new file mode 100644 index 00000000..e0b8c9c2 --- /dev/null +++ b/module/dice/fateRoll.mjs @@ -0,0 +1,101 @@ +import D20RollDialog from '../applications/dialogs/d20RollDialog.mjs'; +import D20Roll from './d20Roll.mjs'; +import { setDiceSoNiceForHopeFateRoll, setDiceSoNiceForFearFateRoll } from '../helpers/utils.mjs'; + +export default class FateRoll extends D20Roll { + constructor(formula, data = {}, options = {}) { + super(formula, data, options); + } + + static messageType = 'fateRoll'; + + static DefaultDialog = D20RollDialog; + + get title() { + return game.i18n.localize( + `DAGGERHEART.GENERAL.fateRoll` + ); + } + + get dHope() { + // if ( !(this.terms[0] instanceof foundry.dice.terms.Die) ) return; + if (!(this.dice[0] instanceof foundry.dice.terms.Die)) this.createBaseDice(); + return this.dice[0]; + // return this.#hopeDice; + } + + set dHope(faces) { + if (!(this.dice[0] instanceof foundry.dice.terms.Die)) this.createBaseDice(); + this.dice[0].faces = this.getFaces(faces); + // this.#hopeDice = `d${face}`; + } + + get dFear() { + // if ( !(this.terms[1] instanceof foundry.dice.terms.Die) ) return; + if (!(this.dice[0] instanceof foundry.dice.terms.Die)) this.createBaseDice(); + return this.dice[0]; + // return this.#fearDice; + } + + set dFear(faces) { + if (!(this.dice[0] instanceof foundry.dice.terms.Die)) this.createBaseDice(); + this.dice[0].faces = this.getFaces(faces); + // this.#fearDice = `d${face}`; + } + + get isCritical() { + return false; + } + + + get fateDie() { + return this.data.fateType; + } + + static getHooks(hooks) { + return [...(hooks ?? []), 'Fate']; + } + + /** @inheritDoc */ + static fromData(data) { + data.terms[0].class = foundry.dice.terms.Die.name; + return super.fromData(data); + } + + createBaseDice() { + if (this.dice[0] instanceof foundry.dice.terms.Die) { + this.terms = [this.terms[0]]; + return; + } + this.terms[0] = new foundry.dice.terms.Die({ faces: 12 }); + } + + static async buildEvaluate(roll, config = {}, message = {}) { + await super.buildEvaluate(roll, config, message); + + if (roll.fateDie === "Hope") { + await setDiceSoNiceForHopeFateRoll( + roll, + config.roll.fate.dice + ); + } else { + await setDiceSoNiceForFearFateRoll( + roll, + config.roll.fate.dice + ); + } + } + + static postEvaluate(roll, config = {}) { + const data = super.postEvaluate(roll, config); + + data.fate = { + dice: roll.fateDie === "Hope" ? roll.dHope.denomination : roll.dFear.denomination, + value: roll.fateDie === "Hope" ? roll.dHope.total : roll.dFear.total, + fateDie: roll.fateDie + }; + + return data; + } + +} diff --git a/module/documents/chatMessage.mjs b/module/documents/chatMessage.mjs index 7e313891..c250e611 100644 --- a/module/documents/chatMessage.mjs +++ b/module/documents/chatMessage.mjs @@ -87,6 +87,15 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage { break; } } + if (this.type === 'fateRoll') { + html.classList.add('fate'); + if (this.system.roll?.fate.fateDie == 'Hope') { + html.classList.add('hope'); + } + if (this.system.roll?.fate.fateDie == 'Fear') { + html.classList.add('fear'); + } + } const autoExpandRoll = game.settings.get( CONFIG.DH.id, diff --git a/module/enrichers/DualityRollEnricher.mjs b/module/enrichers/DualityRollEnricher.mjs index 1d6404ff..b56984fc 100644 --- a/module/enrichers/DualityRollEnricher.mjs +++ b/module/enrichers/DualityRollEnricher.mjs @@ -105,4 +105,5 @@ export const enrichedDualityRoll = async ( config.source = { actor: null }; await CONFIG.Dice.daggerheart.DualityRoll.build(config); } + return config; }; diff --git a/module/enrichers/FateRollEnricher.mjs b/module/enrichers/FateRollEnricher.mjs new file mode 100644 index 00000000..6d24ae18 --- /dev/null +++ b/module/enrichers/FateRollEnricher.mjs @@ -0,0 +1,97 @@ +import { getCommandTarget, rollCommandToJSON } from '../helpers/utils.mjs'; + +export default function DhFateRollEnricher(match, _options) { + const roll = rollCommandToJSON(match[1], match[0]); + if (!roll) return match[0]; + + const fateTypeFromRoll = getFateType(roll?.type); + + if (fateTypeFromRoll == "BAD") { + ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.fateTypeParsing')); + return; + } + + return getFateMessage(roll.result, roll?.flavor); +} + +export function getFateType(fateTypeValue) { + const fateTypeFromValue = fateTypeValue ? + (fateTypeValue.toLowerCase() == "fear" ? "Fear" : + (fateTypeValue.toLowerCase() == "hope" ? "Hope" : "BAD")) : "Hope"; + + + return fateTypeFromValue; +} + +function getFateMessage(roll, flavor) { + const fateType = getFateType(roll?.type); + + if (fateType == "BAD") { + ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.fateTypeParsing')); + return ''; + } + + const fateTypeLocalized = fateType === "Hope" ? game.i18n.localize("DAGGERHEART.GENERAL.hope") : game.i18n.localize("DAGGERHEART.GENERAL.fear"); + + const title = flavor ?? fateTypeLocalized + ' ' + + game.i18n.localize('DAGGERHEART.GENERAL.fate') + ' ' + + game.i18n.localize('DAGGERHEART.GENERAL.roll'); + + const dataLabel = game.i18n.localize('DAGGERHEART.GENERAL.fate'); + + const fateElement = document.createElement('span'); + fateElement.innerHTML = ` + + `; + + return fateElement; +} + +export const renderFateButton = async event => { + const button = event.currentTarget, + target = getCommandTarget({ allowNull: true }); + console.log("button", button); + + const fateTypeFromButton = getFateType(button.dataset?.fatetype); + + if (fateTypeFromButton == "BAD") { + ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.fateTypeParsing')); + return; + } + + await enrichedFateRoll( + { + target, + title: button.dataset.title, + label: button.dataset.label, + fateType: fateTypeFromButton + }, + event + ); +}; + +export const enrichedFateRoll = async ( + { target, title, label, fateType }, + event +) => { + const config = { + event: event ?? {}, + title: title, + roll: { + label: label, + }, + hasRoll: true, + fateType: fateType + }; + + config.data = { experiences: {}, traits: {}, fateType: fateType }; + config.source = { actor: target?.uuid }; + await CONFIG.Dice.daggerheart.FateRoll.build(config); + return config; +}; diff --git a/module/enrichers/_module.mjs b/module/enrichers/_module.mjs index 0dcd870e..b80f166c 100644 --- a/module/enrichers/_module.mjs +++ b/module/enrichers/_module.mjs @@ -1,10 +1,11 @@ import { default as DhDamageEnricher, renderDamageButton } from './DamageEnricher.mjs'; import { default as DhDualityRollEnricher, renderDualityButton } from './DualityRollEnricher.mjs'; +import { default as DhFateRollEnricher, renderFateButton } from './FateRollEnricher.mjs'; import { default as DhEffectEnricher } from './EffectEnricher.mjs'; import { default as DhTemplateEnricher, renderMeasuredTemplate } from './TemplateEnricher.mjs'; import { default as DhLookupEnricher } from './LookupEnricher.mjs'; -export { DhDamageEnricher, DhDualityRollEnricher, DhEffectEnricher, DhTemplateEnricher }; +export { DhDamageEnricher, DhDualityRollEnricher, DhEffectEnricher, DhTemplateEnricher, DhFateRollEnricher }; export const enricherConfig = [ { @@ -15,6 +16,10 @@ export const enricherConfig = [ pattern: /\[\[\/dr\s?(.*?)\]\]({[^}]*})?/g, enricher: DhDualityRollEnricher }, + { + pattern: /\[\[\/fr\s?(.*?)\]\]({[^}]*})?/g, + enricher: DhFateRollEnricher + }, { pattern: /@Effect\[([^\[\]]*)\]({[^}]*})?/g, enricher: DhEffectEnricher @@ -38,6 +43,10 @@ export const enricherRenderSetup = element => { .querySelectorAll('.duality-roll-button') .forEach(element => element.addEventListener('click', renderDualityButton)); + element + .querySelectorAll('.fate-roll-button') + .forEach(element => element.addEventListener('click', renderFateButton)); + element .querySelectorAll('.measured-template-button') .forEach(element => element.addEventListener('click', renderMeasuredTemplate)); diff --git a/module/helpers/utils.mjs b/module/helpers/utils.mjs index a28725b1..234b68b4 100644 --- a/module/helpers/utils.mjs +++ b/module/helpers/utils.mjs @@ -1,4 +1,4 @@ -import { diceTypes, getDiceSoNicePresets, range } from '../config/generalConfig.mjs'; +import { diceTypes, getDiceSoNicePresets, getDiceSoNicePreset, range } from '../config/generalConfig.mjs'; import Tagify from '@yaireo/tagify'; export const capitalize = string => { @@ -69,6 +69,20 @@ export const setDiceSoNiceForDualityRoll = async (rollResult, advantageState, ho } }; +export const setDiceSoNiceForHopeFateRoll = async (rollResult, hopeFaces) => { + if (!game.modules.get('dice-so-nice')?.active) return; + const { diceSoNice } = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance); + const diceSoNicePresets = await getDiceSoNicePreset(diceSoNice.hope, hopeFaces); + rollResult.dice[0].options = diceSoNicePresets; +}; + +export const setDiceSoNiceForFearFateRoll = async (rollResult, fearFaces) => { + if (!game.modules.get('dice-so-nice')?.active) return; + const { diceSoNice } = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance); + const diceSoNicePresets = await getDiceSoNicePreset(diceSoNice.fear, fearFaces); + rollResult.dice[0].options = diceSoNicePresets; +}; + export const chunkify = (array, chunkSize, mappingFunc) => { var chunkifiedArray = []; for (let i = 0; i < array.length; i += chunkSize) { diff --git a/styles/less/global/enrichment.less b/styles/less/global/enrichment.less index 2ad3975a..8256d60a 100644 --- a/styles/less/global/enrichment.less +++ b/styles/less/global/enrichment.less @@ -1,5 +1,6 @@ .measured-template-button, .enriched-damage-button, +.fate-roll-button, .duality-roll-button { display: inline; diff --git a/styles/less/ui/chat/chat.less b/styles/less/ui/chat/chat.less index 6f0e5e85..714add13 100644 --- a/styles/less/ui/chat/chat.less +++ b/styles/less/ui/chat/chat.less @@ -14,6 +14,7 @@ color: @dark; } + &.fate, &.duality { background-image: url(../assets/parchments/dh-parchment-dark.png); @@ -66,6 +67,7 @@ } } + &.fate, &.critical { --text-color: @chat-purple; --bg-color: @chat-purple-40; @@ -80,7 +82,7 @@ } } - &:not(.duality) { + &:not(.duality .fate) { .font-20 { color: @dark; } @@ -173,6 +175,28 @@ } } + &.fate { + + &.hope { + --text-color: @golden; + --bg-color: @golden-40; + .message-header, + .message-content { + background-color: @golden-bg; + } + } + + &.fear { + --text-color: @chat-blue; + --bg-color: @chat-blue-40; + .message-header, + .message-content { + background-color: @chat-blue-bg; + } + } + + } + &.duality { &.hope { --text-color: @golden; diff --git a/system.json b/system.json index 5570bdbf..a50712c4 100644 --- a/system.json +++ b/system.json @@ -285,6 +285,7 @@ }, "ChatMessage": { "dualityRoll": {}, + "fateRoll": {}, "adversaryRoll": {}, "damageRoll": {}, "abilityUse": {}, diff --git a/templates/dialogs/dice-roll/rollSelection.hbs b/templates/dialogs/dice-roll/rollSelection.hbs index c7a9b0f9..a0ad1e56 100644 --- a/templates/dialogs/dice-roll/rollSelection.hbs +++ b/templates/dialogs/dice-roll/rollSelection.hbs @@ -68,82 +68,115 @@ {{/if}} {{/if}} {{/if}} + {{#if (eq @root.rollType 'FateRoll')}} + {{#if (eq @root.roll.fateDie 'Hope')}} + +