diff --git a/lang/en.json b/lang/en.json index a2d4bfd8..bfaac070 100755 --- a/lang/en.json +++ b/lang/en.json @@ -686,11 +686,11 @@ } }, "RollTypes": { - "ability": { - "name": "Ability" + "trait": { + "name": "Trait" }, - "weapon": { - "name": "Weapon" + "attack": { + "name": "Attack" }, "spellcast": { "name": "SpellCast" diff --git a/module/applications/ui/chatLog.mjs b/module/applications/ui/chatLog.mjs index f295d2a6..aa604dda 100644 --- a/module/applications/ui/chatLog.mjs +++ b/module/applications/ui/chatLog.mjs @@ -14,8 +14,6 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo } addChatListeners = async (app, html, data) => { - super.addChatListeners(app, html, data); - html.querySelectorAll('.duality-action-damage').forEach(element => element.addEventListener('click', event => this.onRollDamage(event, data.message)) ); diff --git a/module/config/generalConfig.mjs b/module/config/generalConfig.mjs index 4216f631..3522b41a 100644 --- a/module/config/generalConfig.mjs +++ b/module/config/generalConfig.mjs @@ -388,17 +388,17 @@ export const countdownTypes = { } }; export const rollTypes = { - weapon: { - id: 'weapon', - label: 'DAGGERHEART.CONFIG.RollTypes.weapon.name' + attack: { + id: 'attack', + label: 'DAGGERHEART.CONFIG.RollTypes.attack.name' }, spellcast: { id: 'spellcast', label: 'DAGGERHEART.CONFIG.RollTypes.spellcast.name' }, - ability: { - id: 'ability', - label: 'DAGGERHEART.CONFIG.RollTypes.ability.name' + trait: { + id: 'trait', + label: 'DAGGERHEART.CONFIG.RollTypes.trait.name' }, diceSet: { id: 'diceSet', diff --git a/module/data/action/attackAction.mjs b/module/data/action/attackAction.mjs index d953416a..137879b8 100644 --- a/module/data/action/attackAction.mjs +++ b/module/data/action/attackAction.mjs @@ -5,7 +5,7 @@ export default class DHAttackAction extends DHDamageAction { static extraSchemas = [...super.extraSchemas, ...['roll', 'save']]; static getRollType(parent) { - return parent.type === 'weapon' ? 'weapon' : 'spellcast'; + return parent.type === 'weapon' ? 'attack' : 'spellcast'; } get chatTemplate() { @@ -21,7 +21,7 @@ export default class DHAttackAction extends DHDamageAction { } if (this.roll.useDefault) { this.roll.trait = this.item.system.attack.roll.trait; - this.roll.type = 'weapon'; + this.roll.type = 'attack'; } } } @@ -37,4 +37,8 @@ export default class DHAttackAction extends DHDamageAction { base: true }; } + + // get modifiers() { + // return []; + // } } diff --git a/module/data/action/baseAction.mjs b/module/data/action/baseAction.mjs index 6fdadfe2..4140e024 100644 --- a/module/data/action/baseAction.mjs +++ b/module/data/action/baseAction.mjs @@ -150,7 +150,7 @@ export default class DHBaseAction extends foundry.abstract.DataModel { } static getRollType(parent) { - return 'ability'; + return 'trait'; } static getSourceConfig(parent) { @@ -308,7 +308,7 @@ export default class DHBaseAction extends foundry.abstract.DataModel { prepareRoll() { const roll = { - modifiers: [], + modifiers: this.modifiers, trait: this.roll?.trait, label: 'Attack', type: this.actionType, @@ -362,6 +362,13 @@ export default class DHBaseAction extends foundry.abstract.DataModel { get hasRoll() { return !!this.roll?.type || !!this.roll?.bonus; } + + get modifiers() { + if(!this.actor) return []; + const modifiers = []; + /** Placeholder for specific bonuses **/ + return modifiers; + } /* ROLL */ /* SAVE */ diff --git a/module/data/action/damageAction.mjs b/module/data/action/damageAction.mjs index 7f7faeee..492c4184 100644 --- a/module/data/action/damageAction.mjs +++ b/module/data/action/damageAction.mjs @@ -28,6 +28,7 @@ export default class DHDamageAction extends DHBaseAction { hasSave: this.hasSave, isCritical: data.system?.roll?.isCritical ?? false, source: data.system?.source, + data: this.getRollData(), damageTypes, event }; @@ -39,4 +40,8 @@ export default class DHDamageAction extends DHBaseAction { roll = CONFIG.Dice.daggerheart.DamageRoll.build(config); } + + // get modifiers() { + // return []; + // } } diff --git a/module/data/action/healingAction.mjs b/module/data/action/healingAction.mjs index 26e53817..4c1366a1 100644 --- a/module/data/action/healingAction.mjs +++ b/module/data/action/healingAction.mjs @@ -39,4 +39,8 @@ export default class DHHealingAction extends DHBaseAction { get chatTemplate() { return 'systems/daggerheart/templates/ui/chat/healing-roll.hbs'; } + + get modifiers() { + return []; + } } diff --git a/module/data/actor/adversary.mjs b/module/data/actor/adversary.mjs index 78e08323..6ecee0a1 100644 --- a/module/data/actor/adversary.mjs +++ b/module/data/actor/adversary.mjs @@ -1,13 +1,7 @@ import DHAdversarySettings from '../../applications/sheets-configs/adversary-settings.mjs'; import ActionField from '../fields/actionField.mjs'; import BaseDataActor from './base.mjs'; - -const resourceField = () => - new foundry.data.fields.SchemaField({ - value: new foundry.data.fields.NumberField({ initial: 0, integer: true }), - max: new foundry.data.fields.NumberField({ initial: 0, integer: true }), - isReversed: new foundry.data.fields.BooleanField({ initial: true }) - }); +import { resourceField, bonusField } from '../fields/actorField.mjs'; export default class DhpAdversary extends BaseDataActor { static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Adversary']; @@ -43,8 +37,8 @@ export default class DhpAdversary extends BaseDataActor { severe: new fields.NumberField({ required: true, initial: 0, integer: true }) }), resources: new fields.SchemaField({ - hitPoints: resourceField(), - stress: resourceField() + hitPoints: resourceField(0, true), + stress: resourceField(0, true) }), attack: new ActionField({ initial: { @@ -59,7 +53,7 @@ export default class DhpAdversary extends BaseDataActor { amount: 1 }, roll: { - type: 'weapon' + type: 'attack' }, damage: { parts: [ @@ -80,9 +74,14 @@ export default class DhpAdversary extends BaseDataActor { }) ), bonuses: new fields.SchemaField({ - difficulty: new fields.SchemaField({ - all: new fields.NumberField({ integer: true, initial: 0 }), - reaction: new fields.NumberField({ integer: true, initial: 0 }) + roll: new fields.SchemaField({ + attack: bonusField(), + action: bonusField(), + reaction: bonusField() + }), + damage: new fields.SchemaField({ + physical: bonusField(), + magical: bonusField() }) }) }; diff --git a/module/data/actor/character.mjs b/module/data/actor/character.mjs index e8322671..0df5dd2f 100644 --- a/module/data/actor/character.mjs +++ b/module/data/actor/character.mjs @@ -2,25 +2,7 @@ import { burden } from '../../config/generalConfig.mjs'; import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs'; import DhLevelData from '../levelData.mjs'; import BaseDataActor from './base.mjs'; - -const attributeField = () => - new foundry.data.fields.SchemaField({ - value: new foundry.data.fields.NumberField({ initial: 0, integer: true }), - tierMarked: new foundry.data.fields.BooleanField({ initial: false }) - }); - -const resourceField = (max, reverse = false) => - new foundry.data.fields.SchemaField({ - value: new foundry.data.fields.NumberField({ initial: 0, integer: true }), - max: new foundry.data.fields.NumberField({ initial: max, integer: true }), - isReversed: new foundry.data.fields.BooleanField({ initial: reverse }) - }); - -const stressDamageReductionRule = () => - new foundry.data.fields.SchemaField({ - enabled: new foundry.data.fields.BooleanField({ required: true, initial: false }), - cost: new foundry.data.fields.NumberField({ integer: true }) - }); +import { attributeField, resourceField, stressDamageReductionRule, bonusField } from '../fields/actorField.mjs'; export default class DhCharacter extends BaseDataActor { static get metadata() { @@ -94,22 +76,25 @@ export default class DhCharacter extends BaseDataActor { levelData: new fields.EmbeddedDataField(DhLevelData), bonuses: new fields.SchemaField({ roll: new fields.SchemaField({ - attack: new fields.NumberField({ integer: true, initial: 0 }), - primaryWeapon: new fields.SchemaField({ - attack: new fields.NumberField({ integer: true, initial: 0 }) - }), - spellcast: new fields.NumberField({ integer: true, initial: 0 }), - action: new fields.NumberField({ integer: true, initial: 0 }), - hopeOrFear: new fields.NumberField({ integer: true, initial: 0 }) + attack: bonusField(), + spellcast: bonusField(), + trait: bonusField(), + action: bonusField(), + reaction: bonusField(), + primaryWeapon: bonusField(), + secondaryWeapon: bonusField() }), damage: new fields.SchemaField({ - all: new fields.NumberField({ integer: true, initial: 0 }), - physical: new fields.NumberField({ integer: true, initial: 0 }), - magic: new fields.NumberField({ integer: true, initial: 0 }), - primaryWeapon: new fields.SchemaField({ - bonus: new fields.NumberField({ integer: true }), - extraDice: new fields.NumberField({ integer: true }) - }) + physical: bonusField(), + magical: bonusField(), + primaryWeapon: bonusField(), + secondaryWeapon: bonusField() + }), + healing: bonusField(), + range: new fields.SchemaField({ + weapon: new fields.NumberField({ integer: true, initial: 0 }), + spell: new fields.NumberField({ integer: true, initial: 0 }), + other: new fields.NumberField({ integer: true, initial: 0 }) }) }), companion: new ForeignDocumentUUIDField({ type: 'Actor', nullable: true, initial: null }), @@ -181,6 +166,11 @@ export default class DhCharacter extends BaseDataActor { return !this.class.value || !this.class.subclass; } + get spellcastModifier() { + const subClasses = this.parent.items.filter(x => x.type === 'subclass') ?? []; + return Math.max(subClasses?.map(sc => this.traits[sc.system.spellcastingTrait]?.value)); + } + get spellcastingModifiers() { return { main: this.class.subclass?.system?.spellcastingTrait, diff --git a/module/data/actor/companion.mjs b/module/data/actor/companion.mjs index ea417bd8..f7e94311 100644 --- a/module/data/actor/companion.mjs +++ b/module/data/actor/companion.mjs @@ -4,6 +4,7 @@ import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs'; import ActionField from '../fields/actionField.mjs'; import { adjustDice, adjustRange } from '../../helpers/utils.mjs'; import DHCompanionSettings from '../../applications/sheets-configs/companion-settings.mjs'; +import { resourceField, bonusField } from '../fields/actorField.mjs'; export default class DhCompanion extends BaseDataActor { static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Companion']; @@ -23,11 +24,7 @@ export default class DhCompanion extends BaseDataActor { ...super.defineSchema(), partner: new ForeignDocumentUUIDField({ type: 'Actor' }), resources: new fields.SchemaField({ - stress: new fields.SchemaField({ - value: new fields.NumberField({ initial: 0, integer: true }), - max: new fields.NumberField({ initial: 3, integer: true }), - isReversed: new foundry.data.fields.BooleanField({ initial: true }) - }), + stress: resourceField(3, true), hope: new fields.NumberField({ initial: 0, integer: true }) }), evasion: new fields.NumberField({ required: true, min: 1, initial: 10, integer: true }), @@ -56,7 +53,7 @@ export default class DhCompanion extends BaseDataActor { amount: 1 }, roll: { - type: 'weapon', + type: 'attack', bonus: 0, trait: 'instinct' }, @@ -74,7 +71,13 @@ export default class DhCompanion extends BaseDataActor { } }), actions: new fields.ArrayField(new ActionField()), - levelData: new fields.EmbeddedDataField(DhLevelData) + levelData: new fields.EmbeddedDataField(DhLevelData), + bonuses: new fields.SchemaField({ + damage: new fields.SchemaField({ + physical: bonusField(), + magical: bonusField() + }) + }) }; } @@ -89,7 +92,7 @@ export default class DhCompanion extends BaseDataActor { } prepareBaseData() { - const partnerSpellcastingModifier = this.partner?.system?.spellcastingModifiers?.main; + const partnerSpellcastingModifier = this.partner?.system?.spellcastModifier; const spellcastingModifier = this.partner?.system?.traits?.[partnerSpellcastingModifier]?.value; this.attack.roll.bonus = spellcastingModifier ?? 0; // Needs to expand on which modifier it is that should be used because of multiclassing; diff --git a/module/data/fields/actorField.mjs b/module/data/fields/actorField.mjs new file mode 100644 index 00000000..dc8dcbac --- /dev/null +++ b/module/data/fields/actorField.mjs @@ -0,0 +1,28 @@ +const fields = foundry.data.fields; + +const attributeField = () => + new fields.SchemaField({ + value: new fields.NumberField({ initial: 0, integer: true }), + tierMarked: new fields.BooleanField({ initial: false }) + }); + +const resourceField = (max = 0, reverse = false) => + new fields.SchemaField({ + value: new fields.NumberField({ initial: 0, integer: true }), + max: new fields.NumberField({ initial: max, integer: true }), + isReversed: new fields.BooleanField({ initial: reverse }) + }); + +const stressDamageReductionRule = () => + new fields.SchemaField({ + enabled: new fields.BooleanField({ required: true, initial: false }), + cost: new fields.NumberField({ integer: true }) + }); + +const bonusField = () => + new fields.SchemaField({ + bonus: new fields.NumberField({ integer: true, initial: 0 }), + dice: new fields.ArrayField(new fields.StringField()) + }) + +export { attributeField, resourceField, stressDamageReductionRule, bonusField }; \ No newline at end of file diff --git a/module/data/item/weapon.mjs b/module/data/item/weapon.mjs index 9fbb3eba..0d0c7f76 100644 --- a/module/data/item/weapon.mjs +++ b/module/data/item/weapon.mjs @@ -50,7 +50,7 @@ export default class DHWeapon extends AttachableItem { }, roll: { trait: 'agility', - type: 'weapon' + type: 'attack' }, damage: { parts: [ diff --git a/module/dice/d20Roll.mjs b/module/dice/d20Roll.mjs index c9e9d428..8e13bf46 100644 --- a/module/dice/d20Roll.mjs +++ b/module/dice/d20Roll.mjs @@ -74,7 +74,6 @@ export default class D20Roll extends DHRoll { } constructFormula(config) { - // this.terms = []; this.createBaseDice(); this.configureModifiers(); this.resetFormula(); @@ -91,7 +90,10 @@ export default class D20Roll extends DHRoll { configureModifiers() { this.applyAdvantage(); - this.applyBaseBonus(); + + this.baseTerms = foundry.utils.deepClone(this.terms); + + this.options.roll.modifiers = this.applyBaseBonus(); this.options.experiences?.forEach(m => { if (this.options.data.experiences?.[m]) @@ -100,13 +102,8 @@ export default class D20Roll extends DHRoll { value: this.options.data.experiences[m].value }); }); - - this.options.roll.modifiers?.forEach(m => { - this.terms.push(...this.formatModifier(m.value)); - }); - - this.baseTerms = foundry.utils.deepClone(this.terms); - + + this.addModifiers(); if (this.options.extraFormula) { this.terms.push( new foundry.dice.terms.OperatorTerm({ operator: '+' }), @@ -125,13 +122,18 @@ export default class D20Roll extends DHRoll { } applyBaseBonus() { - this.options.roll.modifiers = []; - if (!this.options.roll.bonus) return; - this.options.roll.modifiers.push({ - label: 'Bonus to Hit', - value: this.options.roll.bonus - // value: Roll.replaceFormulaData('@attackBonus', this.data) - }); + const modifiers = []; + + if(this.options.roll.bonus) + modifiers.push({ + label: 'Bonus to Hit', + value: this.options.roll.bonus + }); + + modifiers.push(...this.getBonus(`roll.${this.options.type}`, `${this.options.type.capitalize()} Bonus`)); + modifiers.push(...this.getBonus(`roll.${this.options.roll.type}`, `${this.options.roll.type.capitalize()} Bonus`)); + + return modifiers; } static async buildEvaluate(roll, config = {}, message = {}) { diff --git a/module/dice/damageRoll.mjs b/module/dice/damageRoll.mjs index 2182d3d0..8b835583 100644 --- a/module/dice/damageRoll.mjs +++ b/module/dice/damageRoll.mjs @@ -24,8 +24,26 @@ export default class DamageRoll extends DHRoll { } } + applyBaseBonus() { + const modifiers = [], + type = this.options.messageType ?? 'damage'; + + modifiers.push(...this.getBonus(`${type}`, `${type.capitalize()} Bonus`)); + this.options.damageTypes?.forEach(t => { + modifiers.push(...this.getBonus(`${type}.${t}`, `${t.capitalize()} ${type.capitalize()} Bonus`)); + }); + const weapons = ['primaryWeapon', 'secondaryWeapon']; + weapons.forEach(w => { + if(this.options.source.item && this.options.source.item === this.data[w]?.id) + modifiers.push(...this.getBonus(`${type}.${w}`, 'Weapon Bonus')); + }); + + return modifiers; + } + constructFormula(config) { super.constructFormula(config); + if (config.isCritical) { const tmpRoll = new Roll(this._formula)._evaluateSync({ maximize: true }), criticalBonus = tmpRoll.total - this.constructor.calculateTotalModifiers(tmpRoll); diff --git a/module/dice/dhRoll.mjs b/module/dice/dhRoll.mjs index 8744223a..22903d6a 100644 --- a/module/dice/dhRoll.mjs +++ b/module/dice/dhRoll.mjs @@ -4,6 +4,7 @@ export default class DHRoll extends Roll { baseTerms = []; constructor(formula, data, options) { super(formula, data, options); + if(!this.data || !Object.keys(this.data).length) this.data = options.data; } static messageType = 'adversaryRoll'; @@ -99,11 +100,44 @@ export default class DHRoll extends Roll { } formatModifier(modifier) { - const numTerm = modifier < 0 ? '-' : '+'; - return [ - new foundry.dice.terms.OperatorTerm({ operator: numTerm }), - new foundry.dice.terms.NumericTerm({ number: Math.abs(modifier) }) - ]; + if(Array.isArray(modifier)) { + return [ + new foundry.dice.terms.OperatorTerm({ operator: '+' }), + ...this.constructor.parse(modifier.join(' + '), this.options.data) + ]; + } else { + const numTerm = modifier < 0 ? '-' : '+'; + return [ + new foundry.dice.terms.OperatorTerm({ operator: numTerm }), + new foundry.dice.terms.NumericTerm({ number: Math.abs(modifier) }) + ]; + } + } + + applyBaseBonus() { + return []; + } + + addModifiers() { + this.options.roll.modifiers?.forEach(m => { + this.terms.push(...this.formatModifier(m.value)); + }); + } + + getBonus(path, label) { + const bonus = foundry.utils.getProperty(this.data.bonuses, path), + modifiers = []; + if(bonus?.bonus) + modifiers.push({ + label: label, + value: bonus?.bonus + }); + if(bonus?.dice?.length) + modifiers.push({ + label: label, + value: bonus?.dice + }); + return modifiers; } getFaces(faces) { @@ -113,6 +147,9 @@ export default class DHRoll extends Roll { constructFormula(config) { this.terms = Roll.parse(this.options.roll.formula, config.data); + this.options.roll.modifiers = this.applyBaseBonus(); + this.addModifiers(); + if (this.options.extraFormula) { this.terms.push( new foundry.dice.terms.OperatorTerm({ operator: '+' }), diff --git a/module/dice/dualityRoll.mjs b/module/dice/dualityRoll.mjs index 1044b93a..99e4fa42 100644 --- a/module/dice/dualityRoll.mjs +++ b/module/dice/dualityRoll.mjs @@ -119,12 +119,21 @@ export default class DualityRoll extends D20Roll { } applyBaseBonus() { - this.options.roll.modifiers = []; - if (!this.options.roll.trait) return; - this.options.roll.modifiers.push({ - label: `DAGGERHEART.CONFIG.Traits.${this.options.roll.trait}.name`, - value: Roll.replaceFormulaData(`@traits.${this.options.roll.trait}.value`, this.data) + const modifiers = super.applyBaseBonus(); + + if(this.options.roll.trait && this.data.traits[this.options.roll.trait]) + modifiers.unshift({ + label: `DAGGERHEART.CONFIG.Traits.${this.options.roll.trait}.name`, + value: this.data.traits[this.options.roll.trait].value + }); + + const weapons = ['primaryWeapon', 'secondaryWeapon']; + weapons.forEach(w => { + if(this.options.source.item && this.options.source.item === this.data[w]?.id) + modifiers.push(...this.getBonus(`roll.${w}`, 'Weapon Bonus')); }); + + return modifiers; } static postEvaluate(roll, config = {}) { diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index c789064b..73a8a3db 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -371,7 +371,7 @@ export default class DhpActor extends Actor { getRollData() { const rollData = super.getRollData(); rollData.prof = this.system.proficiency ?? 1; - rollData.cast = this.system.spellcast ?? 1; + rollData.cast = this.system.spellcastModifier ?? 1; return rollData; } diff --git a/module/helpers/handlebarsHelper.mjs b/module/helpers/handlebarsHelper.mjs index 407f065e..17326dc2 100644 --- a/module/helpers/handlebarsHelper.mjs +++ b/module/helpers/handlebarsHelper.mjs @@ -39,8 +39,9 @@ export default class RegisterHandlebarsHelpers { } static damageSymbols(damageParts) { - const symbols = new Set(); - damageParts.forEach(part => symbols.add(...CONFIG.DH.GENERAL.damageTypes[part.type].icon)); + const symbols = [...new Set(damageParts.reduce((a, c) => a.concat([...c.type]), []))].map( + p => CONFIG.DH.GENERAL.damageTypes[p].icon + ); return new Handlebars.SafeString(Array.from(symbols).map(symbol => ``)); } diff --git a/module/helpers/utils.mjs b/module/helpers/utils.mjs index 1e694aad..336ecf5b 100644 --- a/module/helpers/utils.mjs +++ b/module/helpers/utils.mjs @@ -306,7 +306,7 @@ export const itemAbleRollParse = (value, actor, item) => { const isItemTarget = value.toLowerCase().startsWith('item.'); const slicedValue = isItemTarget ? value.slice(5) : value; try { - return Roll.safeEval(Roll.replaceFormulaData(slicedValue, isItemTarget ? item : actor)); + return Roll.replaceFormulaData(slicedValue, isItemTarget ? item : actor); } catch (_) { return ''; } diff --git a/styles/less/ui/chat/theme-colorful.less b/styles/less/ui/chat/theme-colorful.less index d9c4e08e..3de5b432 100644 --- a/styles/less/ui/chat/theme-colorful.less +++ b/styles/less/ui/chat/theme-colorful.less @@ -44,12 +44,14 @@ display: flex; gap: 2px; margin-bottom: 4px; + flex-wrap: wrap; .duality-modifier { padding: 2px; border-radius: 6px; border: 1px solid; background: var(--color-dark-6); font-size: 12px; + white-space: nowrap; } } .dice-flavor { diff --git a/templates/dialogs/actionSelect.hbs b/templates/dialogs/actionSelect.hbs index 200ef29c..f2190121 100644 --- a/templates/dialogs/actionSelect.hbs +++ b/templates/dialogs/actionSelect.hbs @@ -5,8 +5,8 @@