diff --git a/lang/en.json b/lang/en.json index 76a2cbaa..0290c3df 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" @@ -1015,8 +1015,8 @@ "allDamage": "All Damage", "physicalDamage": "Physical Damage", "magicalDamage": "Magical Damage", - "primaryDamageBonus": "Primary Weapon Damage", - "primaryDamageDice": "Primary Weapon Extra Damage Dice" + "primaryWeapon": "Primary Weapon Damage", + "secondaryWeapon": "Secondary Weapon Damage" }, "DamageResistance": { "none": "None", @@ -1093,10 +1093,18 @@ "single": "Experience", "plural": "Experiences" }, + "Healing": { + "healingAmount": "Healing Amount" + }, "Neutral": { "full": "None", "short": "no" }, + "Range": { + "other": "Range Increase: Other", + "spell": "Range Increase: Spell", + "weapon": "Range Increase: Weapon" + }, "RefreshType": { "session": "Session", "shortrest": "Short Rest", @@ -1109,9 +1117,11 @@ "Roll": { "attack": "Attack Roll", "primaryWeaponAttack": "Primary Weapon Attack Roll", + "secondaryWeaponAttack": "Secondary Weapon Attack Roll", "spellcast": "Spellcast Roll", + "trait": "Trait Roll", "action": "Action Roll", - "hopeOrFear": "Hope Or Fear Roll" + "reaction": "Reaction Roll" }, "Rules": { "damageReduction": { @@ -1186,6 +1196,7 @@ }, "armorScore": "Armor Score", "activeEffects": "Active Effects", + "armorSlots": "Armor Slots", "attack": "Attack", "basics": "Basics", "bonus": "Bonus", diff --git a/module/applications/sheets/actors/character.mjs b/module/applications/sheets/actors/character.mjs index eae7a223..fcd92842 100644 --- a/module/applications/sheets/actors/character.mjs +++ b/module/applications/sheets/actors/character.mjs @@ -103,6 +103,11 @@ export default class CharacterSheet extends DHBaseActorSheet { htmlElement.querySelectorAll('.inventory-item-quantity').forEach(element => { element.addEventListener('change', this.updateItemQuantity.bind(this)); }); + + // Add listener for armor marks input + htmlElement.querySelectorAll('.armor-marks-input').forEach(element => { + element.addEventListener('change', this.updateArmorMarks.bind(this)); + }); } /** @inheritDoc */ @@ -515,6 +520,16 @@ export default class CharacterSheet extends DHBaseActorSheet { this.render(); } + async updateArmorMarks(event) { + const armor = this.document.system.armor; + if (!armor) return; + + const maxMarks = this.document.system.armorScore; + const value = Math.min(Math.max(Number(event.currentTarget.value), 0), maxMarks); + await armor.update({ 'system.marks.value': value }); + this.render(); + } + /* -------------------------------------------- */ /* Application Clicks Actions */ /* -------------------------------------------- */ 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..39051434 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 ba7d1143..e77a4855 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 = label => - new foundry.data.fields.SchemaField({ - value: new foundry.data.fields.NumberField({ initial: 0, integer: true, label }), - 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']; @@ -58,8 +52,8 @@ export default class DhpAdversary extends BaseDataActor { }) }), resources: new fields.SchemaField({ - hitPoints: resourceField('DAGGERHEART.GENERAL.hitPoints'), - stress: resourceField('DAGGERHEART.GENERAL.stress') + hitPoints: resourceField(0, 'DAGGERHEART.GENERAL.hitPoints', true), + stress: resourceField(0, 'DAGGERHEART.GENERAL.stress', true) }), attack: new ActionField({ initial: { @@ -74,7 +68,7 @@ export default class DhpAdversary extends BaseDataActor { amount: 1 }, roll: { - type: 'weapon' + type: 'attack' }, damage: { parts: [ @@ -95,17 +89,14 @@ export default class DhpAdversary extends BaseDataActor { }) ), bonuses: new fields.SchemaField({ - difficulty: new fields.SchemaField({ - all: new fields.NumberField({ - integer: true, - initial: 0, - label: 'DAGGERHEART.GENERAL.Difficulty.all' - }), - reaction: new fields.NumberField({ - integer: true, - initial: 0, - label: 'DAGGERHEART.GENERAL.Difficulty.reaction' - }) + roll: new fields.SchemaField({ + attack: bonusField('DAGGERHEART.GENERAL.Roll.attack'), + action: bonusField('DAGGERHEART.GENERAL.Roll.action'), + reaction: bonusField('DAGGERHEART.GENERAL.Roll.reaction') + }), + damage: new fields.SchemaField({ + physical: bonusField('DAGGERHEART.GENERAL.Damage.physicalDamage'), + magical: bonusField('DAGGERHEART.GENERAL.Damage.magicalDamage') }) }) }; diff --git a/module/data/actor/character.mjs b/module/data/actor/character.mjs index 84a95516..e25dba85 100644 --- a/module/data/actor/character.mjs +++ b/module/data/actor/character.mjs @@ -2,29 +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 = label => - new foundry.data.fields.SchemaField({ - value: new foundry.data.fields.NumberField({ initial: 0, integer: true, label: label }), - tierMarked: new foundry.data.fields.BooleanField({ initial: false }) - }); - -const resourceField = (max, label, reverse = false) => - new foundry.data.fields.SchemaField({ - value: new foundry.data.fields.NumberField({ initial: 0, integer: true, label: label }), - max: new foundry.data.fields.NumberField({ initial: max, integer: true }), - isReversed: new foundry.data.fields.BooleanField({ initial: reverse }) - }); - -const stressDamageReductionRule = localizationPath => - new foundry.data.fields.SchemaField({ - enabled: new foundry.data.fields.BooleanField({ required: true, initial: false }), - cost: new foundry.data.fields.NumberField({ - integer: true, - label: `${localizationPath}.label`, - hint: `${localizationPath}.hint` - }) - }); +import { attributeField, resourceField, stressDamageReductionRule, bonusField } from '../fields/actorField.mjs'; export default class DhCharacter extends BaseDataActor { static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Character']; @@ -112,59 +90,36 @@ 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, - label: 'DAGGERHEART.GENERAL.Roll.attack' - }), - primaryWeapon: new fields.SchemaField({ - attack: new fields.NumberField({ - integer: true, - initial: 0, - label: 'DAGGERHEART.GENERAL.Roll.primaryWeaponAttack' - }) - }), - spellcast: new fields.NumberField({ - integer: true, - initial: 0, - label: 'DAGGERHEART.GENERAL.Roll.spellcast' - }), - action: new fields.NumberField({ - integer: true, - initial: 0, - label: 'DAGGERHEART.GENERAL.Roll.action' - }), - hopeOrFear: new fields.NumberField({ - integer: true, - initial: 0, - label: 'DAGGERHEART.GENERAL.Roll.hopeOrFear' - }) + attack: bonusField('DAGGERHEART.GENERAL.Roll.attack'), + spellcast: bonusField('DAGGERHEART.GENERAL.Roll.spellcast'), + trait: bonusField('DAGGERHEART.GENERAL.Roll.trait'), + action: bonusField('DAGGERHEART.GENERAL.Roll.action'), + reaction: bonusField('DAGGERHEART.GENERAL.Roll.reaction'), + primaryWeapon: bonusField('DAGGERHEART.GENERAL.Roll.primaryWeaponAttack'), + secondaryWeapon: bonusField('DAGGERHEART.GENERAL.Roll.secondaryWeaponAttack') }), damage: new fields.SchemaField({ - all: new fields.NumberField({ + physical: bonusField('DAGGERHEART.GENERAL.Damage.physicalDamage'), + magical: bonusField('DAGGERHEART.GENERAL.Damage.magicalDamage'), + primaryWeapon: bonusField('DAGGERHEART.GENERAL.Damage.primaryWeapon'), + secondaryWeapon: bonusField('DAGGERHEART.GENERAL.Damage.primaryWeapon') + }), + healing: bonusField('DAGGERHEART.GENERAL.Healing.healingAmount'), + range: new fields.SchemaField({ + weapon: new fields.NumberField({ integer: true, initial: 0, - label: 'DAGGERHEART.GENERAL.Damage.allDamage' + label: 'DAGGERHEART.GENERAL.Range.weapon' }), - physical: new fields.NumberField({ + spell: new fields.NumberField({ integer: true, initial: 0, - label: 'DAGGERHEART.GENERAL.Damage.physicalDamage' + label: 'DAGGERHEART.GENERAL.Range.spell' }), - magic: new fields.NumberField({ + other: new fields.NumberField({ integer: true, initial: 0, - label: 'DAGGERHEART.GENERAL.Damage.magicalDamage' - }), - primaryWeapon: new fields.SchemaField({ - bonus: new fields.NumberField({ - integer: true, - label: 'DAGGERHEART.GENERAL.Damage.primaryDamageBonus' - }), - extraDice: new fields.NumberField({ - integer: true, - label: 'DAGGERHEART.GENERAL.Damage.primaryDamageDice' - }) + label: 'DAGGERHEART.GENERAL.Range.other' }) }) }), @@ -246,6 +201,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, @@ -388,6 +348,8 @@ export default class DhCharacter extends BaseDataActor { } prepareBaseData() { + this.evasion = this.class.value?.system?.evasion ?? 0; + const currentLevel = this.levelData.level.current; const currentTier = currentLevel === 1 diff --git a/module/data/actor/companion.mjs b/module/data/actor/companion.mjs index 8784032e..e94ea73c 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,20 +24,9 @@ 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, 'DAGGERHEART.GENERAL.stress', true), hope: new fields.NumberField({ initial: 0, integer: true, label: 'DAGGERHEART.GENERAL.hope' }) }), - evasion: new fields.NumberField({ - required: true, - min: 1, - initial: 10, - integer: true, - label: 'DAGGERHEART.GENERAL.evasion' - }), experiences: new fields.TypedObjectField( new fields.SchemaField({ name: new fields.StringField({}), @@ -62,7 +52,7 @@ export default class DhCompanion extends BaseDataActor { amount: 1 }, roll: { - type: 'weapon', + type: 'attack', bonus: 0, trait: 'instinct' }, @@ -80,7 +70,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('DAGGERHEART.GENERAL.Damage.physicalDamage'), + magical: bonusField('DAGGERHEART.GENERAL.Damage.magicalDamage') + }) + }) }; } @@ -95,7 +91,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..fe00e251 --- /dev/null +++ b/module/data/fields/actorField.mjs @@ -0,0 +1,32 @@ +const fields = foundry.data.fields; + +const attributeField = label => + new fields.SchemaField({ + value: new fields.NumberField({ initial: 0, integer: true, label }), + tierMarked: new fields.BooleanField({ initial: false }) + }); + +const resourceField = (max = 0, label, reverse = false) => + new fields.SchemaField({ + value: new fields.NumberField({ initial: 0, integer: true, label }), + max: new fields.NumberField({ initial: max, integer: true }), + isReversed: new fields.BooleanField({ initial: reverse }) + }); + +const stressDamageReductionRule = localizationPath => + new fields.SchemaField({ + enabled: new fields.BooleanField({ required: true, initial: false }), + cost: new fields.NumberField({ + integer: true, + label: `${localizationPath}.label`, + hint: `${localizationPath}.hint` + }) + }); + +const bonusField = label => + new fields.SchemaField({ + bonus: new fields.NumberField({ integer: true, initial: 0, label }), + dice: new fields.ArrayField(new fields.StringField()) + }); + +export { attributeField, resourceField, stressDamageReductionRule, bonusField }; 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..004e4806 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]) @@ -101,12 +103,7 @@ export default class D20Roll extends DHRoll { }); }); - 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,20 @@ 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..bfbfc7d5 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..1bd9a6e5 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..d983b2d6 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/sheets/actors/adversary/sidebar.less b/styles/less/sheets/actors/adversary/sidebar.less index c1bd1856..f8a34874 100644 --- a/styles/less/sheets/actors/adversary/sidebar.less +++ b/styles/less/sheets/actors/adversary/sidebar.less @@ -215,7 +215,9 @@ justify-content: center; .status-number { - justify-items: center; + display: flex; + align-items: center; + flex-direction: column; .status-value { position: relative; diff --git a/styles/less/sheets/actors/character/sidebar.less b/styles/less/sheets/actors/character/sidebar.less index f46a9628..2c88d14f 100644 --- a/styles/less/sheets/actors/character/sidebar.less +++ b/styles/less/sheets/actors/character/sidebar.less @@ -174,8 +174,115 @@ gap: 5px; justify-content: center; + .status-bar.armor-slots { + display: flex; + justify-content: center; + position: relative; + width: 95px; + height: 30px; + + .status-label { + padding: 2px 10px; + position: relative; + top: 30px; + height: 22px; + width: 95px; + border-radius: 3px; + background: light-dark(@dark-blue, @golden); + + h4 { + font-weight: bold; + text-align: center; + line-height: 18px; + color: light-dark(@beige, @dark-blue); + font-size: 12px; + } + } + .status-value { + position: absolute; + display: flex; + padding: 0 6px; + font-size: 1.2rem; + align-items: center; + width: 80px; + height: 30px; + justify-content: center; + text-align: center; + z-index: 2; + color: light-dark(@dark-blue, @beige); + border: 1px solid light-dark(@dark-blue, @golden); + border-bottom: none; + border-radius: 6px 6px 0 0; + + input[type='number'] { + background: transparent; + font-size: 1.2rem; + width: 30px; + height: 20px; + text-align: center; + border: none; + outline: 2px solid transparent; + color: light-dark(@dark-blue, @beige); + + &.bar-input { + padding: 0; + color: light-dark(@dark-blue, @beige); + backdrop-filter: none; + background: transparent; + transition: all 0.3s ease; + + &:hover, + &:focus { + background: @semi-transparent-dark-blue; + backdrop-filter: blur(9.5px); + } + } + } + + .bar-label { + width: 30px; + } + } + + .progress-bar { + position: absolute; + appearance: none; + width: 80px; + height: 30px; + border: 1px solid light-dark(@dark-blue, @golden); + border-radius: 6px; + z-index: 1; + background: light-dark(transparent, @dark-blue); + border-bottom: none; + border-radius: 6px 6px 0 0; + + &::-webkit-progress-bar { + border: none; + background: light-dark(transparent, @dark-blue); + } + &::-webkit-progress-value { + background: @gradient-stress; + } + &.stress-color::-webkit-progress-value { + background: @gradient-stress; + } + &::-moz-progress-bar { + background: @gradient-stress; + } + &.stress-color::-moz-progress-bar { + background: @gradient-stress; + } + } + } + .status-number { - justify-items: center; + display: flex; + align-items: center; + flex-direction: column; + + &.armor-slots { + width: 95px; + } .status-value { position: relative; @@ -192,9 +299,33 @@ background: light-dark(transparent, @dark-blue); z-index: 2; - &.armor-slots { - width: 80px; - height: 30px; + input[type='number'] { + background: transparent; + font-size: 1.2rem; + width: 30px; + height: 20px; + text-align: center; + border: none; + outline: 2px solid transparent; + color: light-dark(@dark-blue, @beige); + + &.bar-input { + padding: 0; + color: light-dark(@dark-blue, @beige); + backdrop-filter: none; + background: transparent; + transition: all 0.3s ease; + + &:hover, + &:focus { + background: @semi-transparent-dark-blue; + backdrop-filter: blur(9.5px); + } + } + } + + .bar-label { + width: 30px; } } diff --git a/styles/less/sheets/actors/companion/header.less b/styles/less/sheets/actors/companion/header.less index 832a6050..b343146f 100644 --- a/styles/less/sheets/actors/companion/header.less +++ b/styles/less/sheets/actors/companion/header.less @@ -92,7 +92,6 @@ position: relative; width: 100px; height: 40px; - justify-items: center; .status-label { position: relative; diff --git a/styles/less/sheets/actors/environment/header.less b/styles/less/sheets/actors/environment/header.less index e3ca0eec..1276b276 100644 --- a/styles/less/sheets/actors/environment/header.less +++ b/styles/less/sheets/actors/environment/header.less @@ -60,7 +60,9 @@ } .status-number { - justify-items: center; + display: flex; + align-items: center; + flex-direction: column; .status-value { position: relative; 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 @@ diff --git a/templates/sheets/actors/character/sidebar.hbs b/templates/sheets/actors/character/sidebar.hbs index 7b5659d6..51816443 100644 --- a/templates/sheets/actors/character/sidebar.hbs +++ b/templates/sheets/actors/character/sidebar.hbs @@ -48,18 +48,32 @@ -
-
- {{#if document.system.armor.system.marks}} -

{{document.system.armor.system.marks.value}}/{{document.system.armorScore}}

- {{else}} + {{#if document.system.armor.system.marks}} +
+
+

+

/

+

{{document.system.armorScore}}

+
+ +
+

{{localize "DAGGERHEART.GENERAL.armorSlots"}}

+
+
+ {{else}} +
+

-

- {{/if}} +
+
+

{{localize "DAGGERHEART.GENERAL.armorSlots"}}

+
-
-

Armor Slots

-
-
+ {{/if}}