diff --git a/daggerheart.mjs b/daggerheart.mjs index 4f411b0f..5a6d8193 100644 --- a/daggerheart.mjs +++ b/daggerheart.mjs @@ -41,10 +41,14 @@ Hooks.once('init', () => { ] ); - CONFIG.statusEffects = Object.values(SYSTEM.GENERAL.conditions).map(x => ({ - ...x, - name: game.i18n.localize(x.name) - })); + CONFIG.statusEffects = [ + ...CONFIG.statusEffects, + ...Object.values(SYSTEM.GENERAL.conditions).map(x => ({ + ...x, + name: game.i18n.localize(x.name), + systemEffect: true + })) + ]; CONFIG.Dice.daggerheart = { DualityDie: DualityDie, @@ -108,6 +112,8 @@ Hooks.once('init', () => { } ); + CONFIG.Token.hudClass = applications.hud.DHTokenHUD; + CONFIG.Combat.dataModels = { base: models.DhCombat }; diff --git a/lang/en.json b/lang/en.json index e1006827..80409123 100755 --- a/lang/en.json +++ b/lang/en.json @@ -280,6 +280,11 @@ } } }, + "HUD": { + "tokenHUD": { + "genericEffects": "Foundry Effects" + } + }, "Levelup": { "actions": { "creatureComfort": { @@ -385,6 +390,7 @@ "default": "Default Ownership" } }, + "CONFIG": { "ActionType": { "passive": "Passive", @@ -673,6 +679,10 @@ "name": "Dice Set" } }, + "SelectAction": { + "selectType": "Select Action Type", + "selectAction": "Select Action" + }, "Traits": { "agility": { "name": "Agility", @@ -950,6 +960,10 @@ "stress": { "name": "Stress" } + }, + "Attachments": { + "attachHint": "Drop items here to attach them", + "transferHint": "If checked, this effect will be applied to any actor that owns this Effect's parent Item. The effect is always applied if this Item is attached to another one." } }, "GENERAL": { @@ -969,12 +983,21 @@ "singular": "Character", "plural": "Characters" }, + "Cost": { + "single": "Cost", + "plural": "Costs" + }, "Damage": { "severe": "Severe", "major": "Major", "minor": "Minor", "none": "None" }, + "DamageResistance": { + "none": "None", + "resistance": "Resistance", + "immunity": "Immunity" + }, "DamageThresholds": { "title": "Damage Thresholds", "minor": "Minor", @@ -1074,8 +1097,10 @@ "specialization": "Specialization", "mastery": "Mastery", "optional": "Optional", + "recovery": "Recovery", "setup": "Setup", - "equipment": "Equipment" + "equipment": "Equipment", + "attachments": "Attachments" }, "Tiers": { "singular": "Tier", @@ -1095,6 +1120,8 @@ "burden": "Burden", "check": "{check} Check", "criticalSuccess": "Critical Success", + "damage": "Damage", + "damageType": "Damage Type", "description": "Description", "duality": "Duality", "dualityRoll": "Duality Roll", @@ -1107,17 +1134,25 @@ "inactiveEffects": "Inactive Effects", "inventory": "Inventory", "level": "Level", + "max": "Max", "modifier": "Modifier", "multiclass": "Multiclass", + "none": "None", "quantity": "Quantity", "range": "Range", + "recovery": "Recovery", + "scalable": "Scalable", "stress": "Stress", "take": "Take", "target": "Target", "title": "Title", + "true": "True", "type": "Type", "unarmored": "Unarmored", - "use": "Use" + "use": "Use", + "used": "Used", + "uses": "Uses", + "value": "Value" }, "ITEMS": { "Armor": { @@ -1174,6 +1209,7 @@ "spellcastingTrait": "Spellcasting Trait" }, "Weapon": { + "weaponType": "Weapon Type", "primaryWeapon": "Primary Weapon", "secondaryWeapon": "Secondary Weapon" } @@ -1181,7 +1217,8 @@ "SETTINGS": { "Appearance": { "FIELDS": { - "displayFear": { "label": "Fear Display" } + "displayFear": { "label": "Fear Display" }, + "showGenericStatusEffects": { "label": "Show Foundry Status Effects" } }, "fearDisplay": { "token": "Tokens", @@ -1383,7 +1420,8 @@ "unequip": "Unequip", "sendToVault": "Send to Vault", "sendToLoadout": "Send to Loadout", - "makeDeathMove": "Make a Death Move" + "makeDeathMove": "Make a Death Move", + "rangeAndTarget": "Range & Target" } } } diff --git a/module/applications/_module.mjs b/module/applications/_module.mjs index 82c2866c..d4ceb229 100644 --- a/module/applications/_module.mjs +++ b/module/applications/_module.mjs @@ -1,5 +1,6 @@ export * as characterCreation from './characterCreation/_module.mjs'; export * as dialogs from './dialogs/_module.mjs'; +export * as hud from './hud/_module.mjs'; export * as levelup from './levelup/_module.mjs'; export * as settings from './settings/_module.mjs'; export * as sheets from './sheets/_module.mjs'; diff --git a/module/applications/dialogs/costSelectionDialog.mjs b/module/applications/dialogs/costSelectionDialog.mjs index 026aac46..abb79e6a 100644 --- a/module/applications/dialogs/costSelectionDialog.mjs +++ b/module/applications/dialogs/costSelectionDialog.mjs @@ -11,7 +11,7 @@ export default class CostSelectionDialog extends HandlebarsApplicationMixin(Appl static DEFAULT_OPTIONS = { tag: 'form', - classes: ['daggerheart', 'views', 'damage-selection'], + classes: ['daggerheart', 'dialog', 'dh-style', 'views', 'damage-selection'], position: { width: 400, height: 'auto' diff --git a/module/applications/dialogs/damageDialog.mjs b/module/applications/dialogs/damageDialog.mjs index 442a1491..4030d7a7 100644 --- a/module/applications/dialogs/damageDialog.mjs +++ b/module/applications/dialogs/damageDialog.mjs @@ -11,11 +11,14 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application static DEFAULT_OPTIONS = { tag: 'form', id: 'roll-selection', - classes: ['daggerheart', 'views', 'damage-selection'], + classes: ['daggerheart', 'dialog', 'dh-style', 'views', 'damage-selection'], position: { width: 400, height: 'auto' }, + window: { + icon: 'fa-solid fa-dice' + }, actions: { submitRoll: this.submitRoll }, @@ -34,9 +37,15 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application } }; + get title() { + return game.i18n.localize('DAGGERHEART.EFFECTS.ApplyLocations.damageRoll.name'); + } + async _prepareContext(_options) { const context = await super._prepareContext(_options); - context.title = this.config.title; + context.title = this.config.title + ? this.config.title + : game.i18n.localize('DAGGERHEART.EFFECTS.ApplyLocations.damageRoll.name'); context.extraFormula = this.config.extraFormula; context.formula = this.roll.constructFormula(this.config); return context; diff --git a/module/applications/dialogs/damageReductionDialog.mjs b/module/applications/dialogs/damageReductionDialog.mjs index 0612089d..984106d7 100644 --- a/module/applications/dialogs/damageReductionDialog.mjs +++ b/module/applications/dialogs/damageReductionDialog.mjs @@ -11,7 +11,7 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap this.actor = actor; this.damage = damage; - const canApplyArmor = actor.system.armorApplicableDamageTypes[damageType]; + const canApplyArmor = damageType.every(t => actor.system.armorApplicableDamageTypes[t] === true); const maxArmorMarks = canApplyArmor ? Math.min( actor.system.armorScore - actor.system.armor.system.marks.value, @@ -110,7 +110,7 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap ? { value: this.actor.system.resources.stress.value + selectedStressMarks.length + stressReductionStress, - maxTotal: this.actor.system.resources.stress.maxTotal + max: this.actor.system.resources.stress.max } : null; @@ -197,7 +197,7 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap : 0; const currentStress = this.actor.system.resources.stress.value + selectedStressMarks.length + stressReductionStress; - if (currentStress + stressReduction.cost > this.actor.system.resources.stress.maxTotal) { + if (currentStress + stressReduction.cost > this.actor.system.resources.stress.max) { ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.notEnoughStress')); return; } diff --git a/module/applications/dialogs/damageSelectionDialog.mjs b/module/applications/dialogs/damageSelectionDialog.mjs index 5248e81d..547ba87c 100644 --- a/module/applications/dialogs/damageSelectionDialog.mjs +++ b/module/applications/dialogs/damageSelectionDialog.mjs @@ -23,7 +23,7 @@ export default class DamageSelectionDialog extends HandlebarsApplicationMixin(Ap static DEFAULT_OPTIONS = { tag: 'form', - classes: ['daggerheart', 'views', 'damage-selection'], + classes: ['daggerheart', 'dialog', 'dh-style', 'views', 'damage-selection'], position: { width: 400, height: 'auto' diff --git a/module/applications/hud/_module.mjs b/module/applications/hud/_module.mjs new file mode 100644 index 00000000..70edaf8f --- /dev/null +++ b/module/applications/hud/_module.mjs @@ -0,0 +1 @@ +export { default as DHTokenHUD } from './tokenHud.mjs'; diff --git a/module/applications/hud/tokenHUD.mjs b/module/applications/hud/tokenHUD.mjs new file mode 100644 index 00000000..9a58bab2 --- /dev/null +++ b/module/applications/hud/tokenHUD.mjs @@ -0,0 +1,84 @@ +export default class DHTokenHUD extends TokenHUD { + static DEFAULT_OPTIONS = { + classes: ['daggerheart'] + }; + + /** @override */ + static PARTS = { + hud: { + root: true, + template: 'systems/daggerheart/templates/hud/tokenHUD.hbs' + } + }; + + async _prepareContext(options) { + const context = await super._prepareContext(options); + context.systemStatusEffects = Object.keys(context.statusEffects).reduce((acc, key) => { + const effect = context.statusEffects[key]; + if (effect.systemEffect) acc[key] = effect; + + return acc; + }, {}); + + const useGeneric = game.settings.get( + CONFIG.DH.id, + CONFIG.DH.SETTINGS.gameSettings.appearance + ).showGenericStatusEffects; + context.genericStatusEffects = useGeneric + ? Object.keys(context.statusEffects).reduce((acc, key) => { + const effect = context.statusEffects[key]; + if (!effect.systemEffect) acc[key] = effect; + + return acc; + }, {}) + : null; + + return context; + } + + _getStatusEffectChoices() { + // Include all HUD-enabled status effects + const choices = {}; + for (const status of CONFIG.statusEffects) { + if ( + status.hud === false || + (foundry.utils.getType(status.hud) === 'Object' && + status.hud.actorTypes?.includes(this.document.actor.type) === false) + ) { + continue; + } + choices[status.id] = { + _id: status._id, + id: status.id, + systemEffect: status.systemEffect, + title: game.i18n.localize(status.name ?? /** @deprecated since v12 */ status.label), + src: status.img ?? /** @deprecated since v12 */ status.icon, + isActive: false, + isOverlay: false + }; + } + + // Update the status of effects which are active for the token actor + const activeEffects = this.actor?.effects || []; + for (const effect of activeEffects) { + for (const statusId of effect.statuses) { + const status = choices[statusId]; + if (!status) continue; + if (status._id) { + if (status._id !== effect.id) continue; + } else { + if (effect.statuses.size !== 1) continue; + } + status.isActive = true; + if (effect.getFlag('core', 'overlay')) status.isOverlay = true; + break; + } + } + + // Flag status CSS class + for (const status of Object.values(choices)) { + status.cssClass = [status.isActive ? 'active' : null, status.isOverlay ? 'overlay' : null].filterJoin(' '); + } + return choices; + } +} diff --git a/module/applications/levelup/characterLevelup.mjs b/module/applications/levelup/characterLevelup.mjs index 6dc6f4c3..d6bbe2db 100644 --- a/module/applications/levelup/characterLevelup.mjs +++ b/module/applications/levelup/characterLevelup.mjs @@ -223,8 +223,8 @@ export default class DhCharacterLevelUp extends LevelUpBase { context.achievements = { proficiency: { - old: this.actor.system.proficiency.total, - new: this.actor.system.proficiency.total + achivementProficiency, + old: this.actor.system.proficiency, + new: this.actor.system.proficiency + achivementProficiency, shown: achivementProficiency > 0 }, damageThresholds: { @@ -332,16 +332,16 @@ export default class DhCharacterLevelUp extends LevelUpBase { new: context.achievements.proficiency.new + (advancement.proficiency ?? 0) }, hitPoints: { - old: this.actor.system.resources.hitPoints.maxTotal, - new: this.actor.system.resources.hitPoints.maxTotal + (advancement.hitPoint ?? 0) + old: this.actor.system.resources.hitPoints.max, + new: this.actor.system.resources.hitPoints.max + (advancement.hitPoint ?? 0) }, stress: { - old: this.actor.system.resources.stress.maxTotal, - new: this.actor.system.resources.stress.maxTotal + (advancement.stress ?? 0) + old: this.actor.system.resources.stress.max, + new: this.actor.system.resources.stress.max + (advancement.stress ?? 0) }, evasion: { - old: this.actor.system.evasion.total, - new: this.actor.system.evasion.total + (advancement.evasion ?? 0) + old: this.actor.system.evasion, + new: this.actor.system.evasion + (advancement.evasion ?? 0) } }, traits: Object.keys(this.actor.system.traits).reduce((acc, traitKey) => { @@ -349,8 +349,8 @@ export default class DhCharacterLevelUp extends LevelUpBase { if (!acc) acc = {}; acc[traitKey] = { label: game.i18n.localize(abilities[traitKey].label), - old: this.actor.system.traits[traitKey].total, - new: this.actor.system.traits[traitKey].total + advancement.trait[traitKey] + old: this.actor.system.traits[traitKey].max, + new: this.actor.system.traits[traitKey].max + advancement.trait[traitKey] }; } return acc; diff --git a/module/applications/levelup/companionLevelup.mjs b/module/applications/levelup/companionLevelup.mjs index de411572..2fcc42a0 100644 --- a/module/applications/levelup/companionLevelup.mjs +++ b/module/applications/levelup/companionLevelup.mjs @@ -122,12 +122,12 @@ export default class DhCompanionLevelUp extends BaseLevelUp { context.advancements = { statistics: { stress: { - old: this.actor.system.resources.stress.maxTotal, - new: this.actor.system.resources.stress.maxTotal + (advancement.stress ?? 0) + old: this.actor.system.resources.stress.max, + new: this.actor.system.resources.stress.max + (advancement.stress ?? 0) }, evasion: { - old: this.actor.system.evasion.total, - new: this.actor.system.evasion.total + (advancement.evasion ?? 0) + old: this.actor.system.evasion, + new: this.actor.system.evasion + (advancement.evasion ?? 0) } }, experiences: diff --git a/module/applications/levelup/levelup.mjs b/module/applications/levelup/levelup.mjs index 93910fe7..141a0a06 100644 --- a/module/applications/levelup/levelup.mjs +++ b/module/applications/levelup/levelup.mjs @@ -157,8 +157,8 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2) context.achievements = { proficiency: { - old: this.actor.system.proficiency.total, - new: this.actor.system.proficiency.total + achivementProficiency, + old: this.actor.system.proficiency, + new: this.actor.system.proficiency + achivementProficiency, shown: achivementProficiency > 0 }, damageThresholds: { @@ -265,16 +265,16 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2) new: context.achievements.proficiency.new + (advancement.proficiency ?? 0) }, hitPoints: { - old: this.actor.system.resources.hitPoints.maxTotal, - new: this.actor.system.resources.hitPoints.maxTotal + (advancement.hitPoint ?? 0) + old: this.actor.system.resources.hitPoints.max, + new: this.actor.system.resources.hitPoints.max + (advancement.hitPoint ?? 0) }, stress: { - old: this.actor.system.resources.stress.maxTotal, - new: this.actor.system.resources.stress.maxTotal + (advancement.stress ?? 0) + old: this.actor.system.resources.stress.max, + new: this.actor.system.resources.stress.max + (advancement.stress ?? 0) }, evasion: { - old: this.actor.system.evasion.total, - new: this.actor.system.evasion.total + (advancement.evasion ?? 0) + old: this.actor.system.evasion, + new: this.actor.system.evasion + (advancement.evasion ?? 0) } }, traits: Object.keys(this.actor.system.traits).reduce((acc, traitKey) => { @@ -282,8 +282,8 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2) if (!acc) acc = {}; acc[traitKey] = { label: game.i18n.localize(abilities[traitKey].label), - old: this.actor.system.traits[traitKey].total, - new: this.actor.system.traits[traitKey].total + advancement.trait[traitKey] + old: this.actor.system.traits[traitKey].value, + new: this.actor.system.traits[traitKey].value + advancement.trait[traitKey] }; } return acc; diff --git a/module/applications/sheets-configs/action-config.mjs b/module/applications/sheets-configs/action-config.mjs index 0beb8d79..8823fa07 100644 --- a/module/applications/sheets-configs/action-config.mjs +++ b/module/applications/sheets-configs/action-config.mjs @@ -56,10 +56,6 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) { id: 'effect', template: 'systems/daggerheart/templates/sheets-settings/action-settings/effect.hbs' } - /* form: { - id: 'action', - template: 'systems/daggerheart/templates/config/action.hbs' - } */ }; static TABS = { @@ -161,7 +157,7 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) { container = foundry.utils.getProperty(this.action.parent, this.action.systemPath); let newActions; if (Array.isArray(container)) { - newActions = foundry.utils.getProperty(this.action.parent, this.action.systemPath).map(x => x.toObject()); // Find better way + newActions = foundry.utils.getProperty(this.action.parent, this.action.systemPath).map(x => x.toObject()); if (!newActions.findSplice(x => x._id === data._id, data)) newActions.push(data); } else newActions = data; diff --git a/module/applications/sheets/actors/character.mjs b/module/applications/sheets/actors/character.mjs index 4f144645..696169e0 100644 --- a/module/applications/sheets/actors/character.mjs +++ b/module/applications/sheets/actors/character.mjs @@ -27,7 +27,12 @@ export default class CharacterSheet extends DHBaseActorSheet { window: { resizable: true }, - dragDrop: [], + dragDrop: [ + { + dragSelector: '[data-item-id][draggable="true"]', + dropSelector: null + } + ], contextMenus: [{ handler: CharacterSheet.#getDomainCardContextOptions, selector: '[data-item-uuid][data-type="domainCard"]', @@ -599,7 +604,59 @@ export default class CharacterSheet extends DHBaseActorSheet { await doc?.update({ 'system.inVault': !doc.system.inVault }); } + /** + * Use a item + * @type {ApplicationClickAction} + */ + static async useItem(event, button) { + const item = this.getItem(button); + if (!item) return; + + // Should dandle its actions. Or maybe they'll be separate buttons as per an Issue on the board + if (item.type === 'feature') { + item.use(event); + } else if (item instanceof ActiveEffect) { + item.toChat(this); + } else { + const wasUsed = await item.use(event); + if (wasUsed && item.type === 'weapon') { + Hooks.callAll(CONFIG.DH.HOOKS.characterAttack, {}); + } + } + } + + /** + * Use an action + * @type {ApplicationClickAction} + */ + static async useAction(event, button) { + const item = this.getItem(button); + if (!item) return; + + const action = item.system.actions.find(x => x.id === button.dataset.actionId); + if (!action) return; + + action.use(event); + } + + async _onDragStart(event) { + const item = this.getItem(event); + + const dragData = { + type: item.documentName, + uuid: item.uuid + }; + + event.dataTransfer.setData('text/plain', JSON.stringify(dragData)); + + super._onDragStart(event); + } + async _onDrop(event) { + // Prevent event bubbling to avoid duplicate handling + event.preventDefault(); + event.stopPropagation(); + super._onDrop(event); this._onDropItem(event, TextEditor.getDragEventData(event)); } diff --git a/module/applications/sheets/api/_modules.mjs b/module/applications/sheets/api/_modules.mjs index cb7eee62..e6caece1 100644 --- a/module/applications/sheets/api/_modules.mjs +++ b/module/applications/sheets/api/_modules.mjs @@ -1,5 +1,6 @@ export { default as DHApplicationMixin } from './application-mixin.mjs'; export { default as DHBaseItemSheet } from './base-item.mjs'; export { default as DHHeritageSheet } from './heritage-sheet.mjs'; +export { default as DHItemAttachmentSheet } from './item-attachment-sheet.mjs'; export { default as DHBaseActorSheet } from './base-actor.mjs'; export { default as DHBaseActorSettings } from './actor-setting.mjs'; diff --git a/module/applications/sheets/api/base-actor.mjs b/module/applications/sheets/api/base-actor.mjs index 0331f617..be13f5fa 100644 --- a/module/applications/sheets/api/base-actor.mjs +++ b/module/applications/sheets/api/base-actor.mjs @@ -126,7 +126,7 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) { const systemData = { name: game.i18n.localize('DAGGERHEART.GENERAL.Experience.single'), - description: `${experience.name} ${experience.total < 0 ? experience.total : `+${experience.total}`}` + description: `${experience.name} ${experience.value.signedString()}` }; foundry.documents.ChatMessage.implementation.create({ diff --git a/module/applications/sheets/api/item-attachment-sheet.mjs b/module/applications/sheets/api/item-attachment-sheet.mjs new file mode 100644 index 00000000..e89c7cba --- /dev/null +++ b/module/applications/sheets/api/item-attachment-sheet.mjs @@ -0,0 +1,90 @@ + +export default function ItemAttachmentSheet(Base) { + return class extends Base { + static DEFAULT_OPTIONS = { + ...super.DEFAULT_OPTIONS, + dragDrop: [ + ...(super.DEFAULT_OPTIONS.dragDrop || []), + { dragSelector: null, dropSelector: '.attachments-section' } + ], + actions: { + ...super.DEFAULT_OPTIONS.actions, + removeAttachment: this.#removeAttachment + } + }; + + static PARTS = { + ...super.PARTS, + attachments: { + template: 'systems/daggerheart/templates/sheets/global/tabs/tab-attachments.hbs', + scrollable: ['.attachments'] + } + }; + + static TABS = { + ...super.TABS, + primary: { + ...super.TABS?.primary, + tabs: [ + ...(super.TABS?.primary?.tabs || []), + { id: 'attachments' } + ], + initial: super.TABS?.primary?.initial || 'description', + labelPrefix: super.TABS?.primary?.labelPrefix || 'DAGGERHEART.GENERAL.Tabs' + } + }; + + async _preparePartContext(partId, context) { + await super._preparePartContext(partId, context); + + if (partId === 'attachments') { + context.attachedItems = await prepareAttachmentContext(this.document); + } + + return context; + } + + async _onDrop(event) { + const data = TextEditor.getDragEventData(event); + + const attachmentsSection = event.target.closest('.attachments-section'); + if (!attachmentsSection) return super._onDrop(event); + + event.preventDefault(); + event.stopPropagation(); + + const item = await Item.implementation.fromDropData(data); + if (!item) return; + + // Call the data model's public method + await this.document.system.addAttachment(item); + } + + + static async #removeAttachment(event, target) { + // Call the data model's public method + await this.document.system.removeAttachment(target.dataset.uuid); +} + + async _preparePartContext(partId, context) { + await super._preparePartContext(partId, context); + + if (partId === 'attachments') { + // Keep this simple UI preparation in the mixin + const attachedUUIDs = this.document.system.attached; + context.attachedItems = await Promise.all( + attachedUUIDs.map(async uuid => { + const item = await fromUuid(uuid); + return { + uuid: uuid, + name: item?.name || 'Unknown Item', + img: item?.img || 'icons/svg/item-bag.svg' + }; + }) + ); + } + + return context; + } + }; +} \ No newline at end of file diff --git a/module/applications/sheets/items/armor.mjs b/module/applications/sheets/items/armor.mjs index e612f665..80bbe139 100644 --- a/module/applications/sheets/items/armor.mjs +++ b/module/applications/sheets/items/armor.mjs @@ -1,10 +1,10 @@ import DHBaseItemSheet from '../api/base-item.mjs'; +import ItemAttachmentSheet from '../api/item-attachment-sheet.mjs'; -export default class ArmorSheet extends DHBaseItemSheet { +export default class ArmorSheet extends ItemAttachmentSheet(DHBaseItemSheet) { /**@inheritdoc */ static DEFAULT_OPTIONS = { classes: ['armor'], - dragDrop: [{ dragSelector: null, dropSelector: null }], tagifyConfigs: [ { selector: '.features-input', @@ -30,7 +30,8 @@ export default class ArmorSheet extends DHBaseItemSheet { effects: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-effects.hbs', scrollable: ['.effects'] - } + }, + ...super.PARTS, }; /**@inheritdoc */ diff --git a/module/applications/sheets/items/weapon.mjs b/module/applications/sheets/items/weapon.mjs index af1c7eb4..3fb725d7 100644 --- a/module/applications/sheets/items/weapon.mjs +++ b/module/applications/sheets/items/weapon.mjs @@ -1,6 +1,7 @@ import DHBaseItemSheet from '../api/base-item.mjs'; +import ItemAttachmentSheet from '../api/item-attachment-sheet.mjs'; -export default class WeaponSheet extends DHBaseItemSheet { +export default class WeaponSheet extends ItemAttachmentSheet(DHBaseItemSheet) { /**@inheritdoc */ static DEFAULT_OPTIONS = { classes: ['weapon'], @@ -29,12 +30,13 @@ export default class WeaponSheet extends DHBaseItemSheet { effects: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-effects.hbs', scrollable: ['.effects'] - } + }, + ...super.PARTS, }; /**@inheritdoc */ async _preparePartContext(partId, context) { - super._preparePartContext(partId, context); + await super._preparePartContext(partId, context); switch (partId) { case 'settings': context.features = this.document.system.weaponFeatures.map(x => x.value); diff --git a/module/applications/ui/chatLog.mjs b/module/applications/ui/chatLog.mjs index 71532733..e8f699e4 100644 --- a/module/applications/ui/chatLog.mjs +++ b/module/applications/ui/chatLog.mjs @@ -215,7 +215,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo if (message.system.onSave && message.system.targets.find(t => t.id === target.id)?.saved?.success === true) damage = Math.ceil(damage * (CONFIG.DH.ACTIONS.damageOnSave[message.system.onSave]?.mod ?? 1)); - target.actor.takeDamage(damage, message.system.roll.type); + target.actor.takeDamage(damage, message.system.damage.damageType); } }; diff --git a/module/config/actorConfig.mjs b/module/config/actorConfig.mjs index 7a179308..02cfd4a9 100644 --- a/module/config/actorConfig.mjs +++ b/module/config/actorConfig.mjs @@ -113,7 +113,7 @@ export const adversaryTypes = { }, social: { id: 'social', - label: 'DAGGERHEART.CONFIG.AdversaryTypee.social.label', + label: 'DAGGERHEART.CONFIG.AdversaryType.social.label', description: 'DAGGERHEART.ACTORS.Adversary.social.description' }, solo: { diff --git a/module/config/flagsConfig.mjs b/module/config/flagsConfig.mjs index 252863f1..0c112231 100644 --- a/module/config/flagsConfig.mjs +++ b/module/config/flagsConfig.mjs @@ -7,3 +7,5 @@ export const encounterCountdown = { simple: 'countdown-encounter-simple', position: 'countdown-encounter-position' }; + +export const itemAttachmentSource = 'attachmentSource'; \ No newline at end of file diff --git a/module/config/generalConfig.mjs b/module/config/generalConfig.mjs index 282a8c9c..94efec1b 100644 --- a/module/config/generalConfig.mjs +++ b/module/config/generalConfig.mjs @@ -59,13 +59,13 @@ export const damageTypes = { id: 'physical', label: 'DAGGERHEART.CONFIG.DamageType.physical.name', abbreviation: 'DAGGERHEART.CONFIG.DamageType.physical.abbreviation', - icon: ['fa-hand-fist'] + icon: 'fa-hand-fist' }, magical: { id: 'magical', label: 'DAGGERHEART.CONFIG.DamageType.magical.name', abbreviation: 'DAGGERHEART.CONFIG.DamageType.magical.abbreviation', - icon: ['fa-wand-sparkles'] + icon: 'fa-wand-sparkles' } }; diff --git a/module/config/itemConfig.mjs b/module/config/itemConfig.mjs index cdc8a235..1d6b96b8 100644 --- a/module/config/itemConfig.mjs +++ b/module/config/itemConfig.mjs @@ -41,37 +41,37 @@ export const armorFeatures = { img: 'icons/magic/control/buff-flight-wings-red.webp', changes: [ { - key: 'system.traits.agility.bonus', + key: 'system.traits.agility.value', mode: 2, value: '-1' }, { - key: 'system.traits.strength.bonus', + key: 'system.traits.strength.value', mode: 2, value: '-1' }, { - key: 'system.traits.finesse.bonus', + key: 'system.traits.finesse.value', mode: 2, value: '-1' }, { - key: 'system.traits.instinct.bonus', + key: 'system.traits.instinct.value', mode: 2, value: '-1' }, { - key: 'system.traits.presence.bonus', + key: 'system.traits.presence.value', mode: 2, value: '-1' }, { - key: 'system.traits.knowledge.bonus', + key: 'system.traits.knowledge.value', mode: 2, value: '-1' }, { - key: 'system.evasion.bonus', + key: 'system.evasion', mode: 2, value: '-1' } @@ -89,7 +89,7 @@ export const armorFeatures = { img: 'icons/magic/movement/abstract-ribbons-red-orange.webp', changes: [ { - key: 'system.evasion.bonus', + key: 'system.evasion', mode: 2, value: '1' } @@ -125,7 +125,7 @@ export const armorFeatures = { img: 'icons/magic/control/control-influence-crown-gold.webp', changes: [ { - key: 'system.traits.presence.bonus', + key: 'system.traits.presence.value', mode: 2, value: '1' } @@ -143,7 +143,7 @@ export const armorFeatures = { img: 'icons/commodities/metal/ingot-worn-iron.webp', changes: [ { - key: 'system.evasion.bonus', + key: 'system.evasion', mode: 2, value: '-1' } @@ -372,12 +372,12 @@ export const armorFeatures = { img: 'icons/commodities/metal/ingot-stamped-steel.webp', changes: [ { - key: 'system.evasion.bonus', + key: 'system.evasion', mode: 2, value: '-2' }, { - key: 'system.traits.agility.bonus', + key: 'system.traits.agility.value', mode: 2, value: '-1' } @@ -395,7 +395,7 @@ export const armorFeatures = { img: 'icons/magic/defensive/barrier-shield-dome-pink.webp', changes: [ { - key: 'system.bonuses.damageReduction.magical', + key: 'system.resistance.magical.reduction', mode: 2, value: '@system.armorScore' } @@ -413,7 +413,7 @@ export const weaponFeatures = { { changes: [ { - key: 'system.bonuses.armorScore', + key: 'system.armorScore', mode: 2, value: 'ITEM.@system.tier + 1' } @@ -422,7 +422,7 @@ export const weaponFeatures = { { changes: [ { - key: 'system.evasion.bonus', + key: 'system.evasion', mode: 2, value: '-1' } @@ -474,7 +474,7 @@ export const weaponFeatures = { { changes: [ { - key: 'system.evasion.bonus', + key: 'system.evasion', mode: 2, value: '-1' } @@ -529,7 +529,7 @@ export const weaponFeatures = { img: 'icons/magic/lightning/claws-unarmed-strike-teal.webp', changes: [ { - key: 'system.proficiency.bonus', + key: 'system.proficiency', mode: 2, value: '1' } @@ -569,7 +569,7 @@ export const weaponFeatures = { img: 'icons/commodities/metal/mail-plate-steel.webp', changes: [ { - key: 'system.traits.finesse.bonus', + key: 'system.traits.finesse.value', mode: 2, value: '-1' } @@ -615,7 +615,7 @@ export const weaponFeatures = { img: 'icons/skills/melee/hand-grip-sword-strike-orange.webp', changes: [ { - key: 'system.evasion.bonus', + key: 'system.evasion', mode: 2, value: '@system.armorScore' } @@ -645,7 +645,7 @@ export const weaponFeatures = { img: 'icons/skills/melee/strike-flail-spiked-pink.webp', changes: [ { - key: 'system.traits.agility.bonus', + key: 'system.traits.agility.value', mode: 2, value: '-1' } @@ -683,7 +683,7 @@ export const weaponFeatures = { img: 'icons/skills/melee/sword-shield-stylized-white.webp', changes: [ { - key: 'system.bonuses.armorScore', + key: 'system.armorScore', mode: 2, value: '1' }, @@ -777,7 +777,7 @@ export const weaponFeatures = { img: 'icons/commodities/currency/coins-crown-stack-gold.webp', changes: [ { - key: 'system.proficiency.bonus', + key: 'system.proficiency', mode: 2, value: '1' } @@ -819,7 +819,7 @@ export const weaponFeatures = { img: 'icons/commodities/metal/ingot-worn-iron.webp', changes: [ { - key: 'system.evasion.bonus', + key: 'system.evasion', mode: 2, value: '-1' } @@ -941,7 +941,7 @@ export const weaponFeatures = { img: '', changes: [ { - key: 'system.evasion.bonus', + key: 'system.evasion', mode: 2, value: '-1' }, @@ -1031,7 +1031,7 @@ export const weaponFeatures = { img: 'icons/magic/control/hypnosis-mesmerism-eye.webp', changes: [ { - key: 'system.traits.presence.bonus', + key: 'system.traits.presence.value', mode: 2, value: '2' } @@ -1088,7 +1088,7 @@ export const weaponFeatures = { img: 'icons/skills/melee/shield-block-gray-orange.webp', changes: [ { - key: 'system.bonuses.armorScore', + key: 'system.armorScore', mode: 2, value: '1' } @@ -1218,7 +1218,7 @@ export const weaponFeatures = { { key: 'system.bonuses.damage.primaryWeapon.bonus', mode: 2, - value: '@system.traits.agility.total' + value: '@system.traits.agility.value' } ] } diff --git a/module/data/action/actionDice.mjs b/module/data/action/actionDice.mjs index 8b5bbe40..5cc2d10a 100644 --- a/module/data/action/actionDice.mjs +++ b/module/data/action/actionDice.mjs @@ -76,11 +76,7 @@ export class DHActionDiceData extends foundry.abstract.DataModel { }; } - getFormula(actor) { - /* const multiplier = this.multiplier === 'flat' ? this.flatMultiplier : actor.system[this.multiplier]?.total; - return this.custom.enabled - ? this.custom.formula - : `${multiplier ?? 1}${this.dice}${this.bonus ? (this.bonus < 0 ? ` - ${Math.abs(this.bonus)}` : ` + ${this.bonus}`) : ''}`; */ + getFormula() { const multiplier = this.multiplier === 'flat' ? this.flatMultiplier : `@${this.multiplier}`, bonus = this.bonus ? (this.bonus < 0 ? ` - ${Math.abs(this.bonus)}` : ` + ${this.bonus}`) : ''; return this.custom.enabled ? this.custom.formula : `${multiplier ?? 1}${this.dice}${bonus}`; @@ -93,7 +89,6 @@ export class DHDamageField extends fields.SchemaField { parts: new fields.ArrayField(new fields.EmbeddedDataField(DHDamageData)), includeBase: new fields.BooleanField({ initial: false }) }; - // if (hasBase) damageFields.includeBase = new fields.BooleanField({ initial: true }); super(damageFields, options, context); } } @@ -102,15 +97,19 @@ export class DHDamageData extends foundry.abstract.DataModel { /** @override */ static defineSchema() { return { - // ...super.defineSchema(), base: new fields.BooleanField({ initial: false, readonly: true, label: 'Base' }), - type: new fields.StringField({ - choices: CONFIG.DH.GENERAL.damageTypes, - initial: 'physical', - label: 'Type', - nullable: false, - required: true - }), + type: new fields.SetField( + new fields.StringField({ + choices: CONFIG.DH.GENERAL.damageTypes, + initial: 'physical', + nullable: false, + required: true + }), + { + label: 'Type', + initial: 'physical', + } + ), resultBased: new fields.BooleanField({ initial: false, label: 'DAGGERHEART.ACTIONS.Settings.resultBased.label' diff --git a/module/data/action/baseAction.mjs b/module/data/action/baseAction.mjs index e193aefe..ae70bf21 100644 --- a/module/data/action/baseAction.mjs +++ b/module/data/action/baseAction.mjs @@ -179,16 +179,9 @@ export default class DHBaseAction extends foundry.abstract.DataModel { getRollData(data = {}) { const actorData = this.actor.getRollData(false); - // Remove when included directly in Actor getRollData - actorData.prof = actorData.proficiency?.value ?? 1; - actorData.cast = actorData.spellcast?.value ?? 1; + // Add Roll results to RollDatas actorData.result = data.roll?.total ?? 1; - /* actorData.scale = data.costs?.length - ? data.costs.reduce((a, c) => { - a[c.type] = c.value; - return a; - }, {}) - : 1; */ + actorData.scale = data.costs?.length // Right now only return the first scalable cost. ? (data.costs.find(c => c.scalable)?.total ?? 1) : 1; @@ -338,7 +331,7 @@ export default class DHBaseAction extends foundry.abstract.DataModel { .filter(c => c.enabled !== false) .map(c => { const resource = this.actor.system.resources[c.type]; - return { type: c.type, value: (c.total ?? c.value) * (resource.hasOwnProperty('maxTotal') ? 1 : -1) }; + return { type: c.type, value: (c.total ?? c.value) * (resource.isReversed ? 1 : -1) }; }); await this.actor.modifyResource(resources); @@ -391,12 +384,12 @@ export default class DHBaseAction extends foundry.abstract.DataModel { return false; } - /* maxTotal is a sign that the resource is inverted, IE it counts upwards instead of down */ + /* isReversed is a sign that the resource is inverted, IE it counts upwards instead of down */ const resources = this.actor.system.resources; return realCosts.reduce( (a, c) => - a && resources[c.type].hasOwnProperty('maxTotal') - ? resources[c.type].value + (c.total ?? c.value) <= resources[c.type].maxTotal + a && resources[c.type].isReversed + ? resources[c.type].value + (c.total ?? c.value) <= resources[c.type].max : resources[c.type]?.value >= (c.total ?? c.value), true ); @@ -439,7 +432,7 @@ export default class DHBaseAction extends foundry.abstract.DataModel { name: actor.actor.name, img: actor.actor.img, difficulty: actor.actor.system.difficulty, - evasion: actor.actor.system.evasion?.total + evasion: actor.actor.system.evasion }; } /* TARGET */ diff --git a/module/data/action/damageAction.mjs b/module/data/action/damageAction.mjs index 2dd88af4..3e840e60 100644 --- a/module/data/action/damageAction.mjs +++ b/module/data/action/damageAction.mjs @@ -10,7 +10,10 @@ export default class DHDamageAction extends DHBaseAction { } async rollDamage(event, data) { - let formula = this.damage.parts.map(p => this.getFormulaValue(p, data).getFormula(this.actor)).join(' + '); + let formula = this.damage.parts.map(p => this.getFormulaValue(p, data).getFormula(this.actor)).join(' + '), + damageTypes = [...new Set(this.damage.parts.reduce((a,c) => a.concat([...c.type]), []))]; + + damageTypes = !damageTypes.length ? ['physical'] : damageTypes; if (!formula || formula == '') return; let roll = { formula: formula, total: formula }, @@ -25,6 +28,7 @@ export default class DHDamageAction extends DHBaseAction { hasSave: this.hasSave, isCritical: data.system?.roll?.isCritical ?? false, source: data.system?.source, + damageTypes, event }; if (this.hasSave) config.onSave = this.save.damageMod; @@ -32,7 +36,7 @@ export default class DHDamageAction extends DHBaseAction { config.source.message = data._id; config.directDamage = false; } - + roll = CONFIG.Dice.daggerheart.DamageRoll.build(config); } } diff --git a/module/data/actor/adversary.mjs b/module/data/actor/adversary.mjs index 9bb5d5f8..78e08323 100644 --- a/module/data/actor/adversary.mjs +++ b/module/data/actor/adversary.mjs @@ -5,7 +5,8 @@ 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 }) + max: new foundry.data.fields.NumberField({ initial: 0, integer: true }), + isReversed: new foundry.data.fields.BooleanField({ initial: true }) }); export default class DhpAdversary extends BaseDataActor { @@ -22,6 +23,7 @@ export default class DhpAdversary extends BaseDataActor { static defineSchema() { const fields = foundry.data.fields; return { + ...super.defineSchema(), tier: new fields.StringField({ required: true, choices: CONFIG.DH.GENERAL.tiers, @@ -32,7 +34,6 @@ export default class DhpAdversary extends BaseDataActor { choices: CONFIG.DH.ACTOR.adversaryTypes, initial: CONFIG.DH.ACTOR.adversaryTypes.standard.id }), - description: new fields.StringField(), motivesAndTactics: new fields.StringField(), notes: new fields.HTMLField(), difficulty: new fields.NumberField({ required: true, initial: 1, integer: true }), @@ -63,6 +64,7 @@ export default class DhpAdversary extends BaseDataActor { damage: { parts: [ { + type: ['physical'], value: { multiplier: 'flat' } @@ -74,7 +76,7 @@ export default class DhpAdversary extends BaseDataActor { experiences: new fields.TypedObjectField( new fields.SchemaField({ name: new fields.StringField(), - total: new fields.NumberField({ required: true, integer: true, initial: 1 }) + value: new fields.NumberField({ required: true, integer: true, initial: 1 }) }) ), bonuses: new fields.SchemaField({ diff --git a/module/data/actor/base.mjs b/module/data/actor/base.mjs index 1958ea28..d7c7213e 100644 --- a/module/data/actor/base.mjs +++ b/module/data/actor/base.mjs @@ -1,5 +1,12 @@ import DHBaseActorSettings from "../../applications/sheets/api/actor-setting.mjs"; +const resistanceField = () => + new foundry.data.fields.SchemaField({ + resistance: new foundry.data.fields.BooleanField({ initial: false }), + immunity: new foundry.data.fields.BooleanField({ initial: false }), + reduction: new foundry.data.fields.NumberField({ integer: true, initial: 0 }) + }); + /** * Describes metadata about the actor data model type * @typedef {Object} ActorDataModelMetadata @@ -16,6 +23,7 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel { type: 'base', isNPC: true, settingSheet: null, + hasResistances: true }; } @@ -24,6 +32,21 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel { return this.constructor.metadata; } + /** @inheritDoc */ + static defineSchema() { + const fields = foundry.data.fields; + const schema = {}; + + if(this.metadata.isNPC) + schema.description = new fields.HTMLField({ required: true, nullable: true }); + if(this.metadata.hasResistances) + schema.resistance = new fields.SchemaField({ + physical: resistanceField(), + magical: resistanceField() + }) + return schema; + } + /** * 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/actor/character.mjs b/module/data/actor/character.mjs index 857cac81..c02f1143 100644 --- a/module/data/actor/character.mjs +++ b/module/data/actor/character.mjs @@ -5,16 +5,15 @@ import BaseDataActor from './base.mjs'; const attributeField = () => new foundry.data.fields.SchemaField({ - value: new foundry.data.fields.NumberField({ initial: null, integer: true }), - bonus: new foundry.data.fields.NumberField({ initial: 0, integer: true }), + value: new foundry.data.fields.NumberField({ initial: 0, integer: true }), tierMarked: new foundry.data.fields.BooleanField({ initial: false }) }); -const resourceField = max => +const resourceField = (max, reverse = false) => new foundry.data.fields.SchemaField({ value: new foundry.data.fields.NumberField({ initial: 0, integer: true }), - bonus: new foundry.data.fields.NumberField({ initial: 0, integer: true }), - max: new foundry.data.fields.NumberField({ initial: max, integer: true }) + max: new foundry.data.fields.NumberField({ initial: max, integer: true }), + isReversed: new foundry.data.fields.BooleanField({ initial: reverse }) }); const stressDamageReductionRule = () => @@ -36,12 +35,10 @@ export default class DhCharacter extends BaseDataActor { const fields = foundry.data.fields; return { + ...super.defineSchema(), resources: new fields.SchemaField({ - hitPoints: new fields.SchemaField({ - value: new foundry.data.fields.NumberField({ initial: 0, integer: true }), - bonus: new foundry.data.fields.NumberField({ initial: 0, integer: true }) - }), - stress: resourceField(6), + hitPoints: resourceField(0, true), + stress: resourceField(6, true), hope: resourceField(6), tokens: new fields.ObjectField(), dice: new fields.ObjectField() @@ -54,18 +51,17 @@ export default class DhCharacter extends BaseDataActor { presence: attributeField(), knowledge: attributeField() }), - proficiency: new fields.SchemaField({ - value: new fields.NumberField({ initial: 1, integer: true }), - bonus: new fields.NumberField({ initial: 0, integer: true }) - }), - evasion: new fields.SchemaField({ - bonus: new fields.NumberField({ initial: 0, integer: true }) + proficiency: new fields.NumberField({ initial: 1, integer: true }), + evasion: new fields.NumberField({ initial: 0, integer: true }), + armorScore: new fields.NumberField({ integer: true, initial: 0 }), + damageThresholds: new fields.SchemaField({ + severe: new fields.NumberField({ integer: true, initial: 0 }), + major: new fields.NumberField({ integer: true, initial: 0 }) }), experiences: new fields.TypedObjectField( new fields.SchemaField({ name: new fields.StringField(), - value: new fields.NumberField({ integer: true, initial: 0 }), - bonus: new fields.NumberField({ integer: true, initial: 0 }) + value: new fields.NumberField({ integer: true, initial: 0 }) }) ), gold: new fields.SchemaField({ @@ -99,15 +95,6 @@ export default class DhCharacter extends BaseDataActor { }), levelData: new fields.EmbeddedDataField(DhLevelData), bonuses: new fields.SchemaField({ - armorScore: new fields.NumberField({ integer: true, initial: 0 }), - damageReduction: new fields.SchemaField({ - physical: new fields.NumberField({ integer: true, initial: 0 }), - magical: new fields.NumberField({ integer: true, initial: 0 }) - }), - damageThresholds: new fields.SchemaField({ - severe: new fields.NumberField({ integer: true, initial: 0 }), - major: new fields.NumberField({ integer: true, initial: 0 }) - }), roll: new fields.SchemaField({ attack: new fields.NumberField({ integer: true, initial: 0 }), primaryWeapon: new fields.SchemaField({ @@ -306,7 +293,7 @@ export default class DhCharacter extends BaseDataActor { get deathMoveViable() { return ( - this.resources.hitPoints.maxTotal > 0 && this.resources.hitPoints.value >= this.resources.hitPoints.maxTotal + this.resources.hitPoints.max > 0 && this.resources.hitPoints.value >= this.resources.hitPoints.max ); } @@ -350,32 +337,32 @@ export default class DhCharacter extends BaseDataActor { for (let levelKey in this.levelData.levelups) { const level = this.levelData.levelups[levelKey]; - this.proficiency.bonus += level.achievements.proficiency; + this.proficiency += level.achievements.proficiency; for (let selection of level.selections) { switch (selection.type) { case 'trait': selection.data.forEach(data => { - this.traits[data].bonus += 1; + this.traits[data].value += 1; this.traits[data].tierMarked = selection.tier === currentTier; }); break; case 'hitPoint': - this.resources.hitPoints.bonus += selection.value; + this.resources.hitPoints.max += selection.value; break; case 'stress': - this.resources.stress.bonus += selection.value; + this.resources.stress.max += selection.value; break; case 'evasion': - this.evasion.bonus += selection.value; + this.evasion += selection.value; break; case 'proficiency': - this.proficiency.bonus = selection.value; + this.proficiency = selection.value; break; case 'experience': Object.keys(this.experiences).forEach(key => { const experience = this.experiences[key]; - experience.bonus += selection.value; + experience.value += selection.value; }); break; } @@ -383,7 +370,7 @@ export default class DhCharacter extends BaseDataActor { } const armor = this.armor; - this.armorScore = this.armor ? this.armor.system.baseScore + (this.bonuses.armorScore ?? 0) : 0; // Bonuses to armorScore won't have been applied yet. Need to solve in documentPreparation somehow + this.armorScore = armor ? armor.system.baseScore : 0; this.damageThresholds = { major: armor ? armor.system.baseThresholds.major + this.levelData.level.current @@ -392,29 +379,12 @@ 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.hitPoints.max = this.class.value?.system?.hitPoints ?? 0; } prepareDerivedData() { - this.resources.hope.max -= Object.keys(this.scars).length; this.resources.hope.value = Math.min(this.resources.hope.value, this.resources.hope.max); - - for (var traitKey in this.traits) { - var trait = this.traits[traitKey]; - trait.total = (trait.value ?? 0) + trait.bonus; - } - - for (var experienceKey in this.experiences) { - var experience = this.experiences[experienceKey]; - experience.total = experience.value + experience.bonus; - } - - this.rules.damageReduction.maxArmorMarked.total = - this.rules.damageReduction.maxArmorMarked.value + this.rules.damageReduction.maxArmorMarked.bonus; - - this.resources.hitPoints.maxTotal = (this.class.value?.system?.hitPoints ?? 0) + this.resources.hitPoints.bonus; - this.resources.stress.maxTotal = this.resources.stress.max + this.resources.stress.bonus; - this.evasion.total = (this.class?.evasion ?? 0) + this.evasion.bonus; - this.proficiency.total = this.proficiency.value + this.proficiency.bonus; } getRollData() { diff --git a/module/data/actor/companion.mjs b/module/data/actor/companion.mjs index 1203cc96..ea417bd8 100644 --- a/module/data/actor/companion.mjs +++ b/module/data/actor/companion.mjs @@ -20,24 +20,21 @@ export default class DhCompanion extends BaseDataActor { const fields = foundry.data.fields; return { + ...super.defineSchema(), partner: new ForeignDocumentUUIDField({ type: 'Actor' }), resources: new fields.SchemaField({ stress: new fields.SchemaField({ value: new fields.NumberField({ initial: 0, integer: true }), - bonus: new fields.NumberField({ initial: 0, integer: true }), - max: new fields.NumberField({ initial: 3, integer: true }) + max: new fields.NumberField({ initial: 3, integer: true }), + isReversed: new foundry.data.fields.BooleanField({ initial: true }) }), hope: new fields.NumberField({ initial: 0, integer: true }) }), - evasion: new fields.SchemaField({ - value: new fields.NumberField({ required: true, min: 1, initial: 10, integer: true }), - bonus: new fields.NumberField({ initial: 0, integer: true }) - }), + evasion: new fields.NumberField({ required: true, min: 1, initial: 10, integer: true }), experiences: new fields.TypedObjectField( new fields.SchemaField({ name: new fields.StringField({}), - value: new fields.NumberField({ integer: true, initial: 0 }), - bonus: new fields.NumberField({ integer: true, initial: 0 }) + value: new fields.NumberField({ integer: true, initial: 0 }) }), { initial: { @@ -66,10 +63,10 @@ export default class DhCompanion extends BaseDataActor { damage: { parts: [ { - multiplier: 'flat', + type: ['physical'], value: { dice: 'd6', - multiplier: 'flat' + multiplier: 'prof' } } ] @@ -83,13 +80,17 @@ export default class DhCompanion extends BaseDataActor { get traits() { return { - instinct: { total: this.attack.roll.bonus } + instinct: { value: this.attack.roll.bonus } }; } + get proficiency() { + return this.partner?.system?.proficiency ?? 1; + } + prepareBaseData() { const partnerSpellcastingModifier = this.partner?.system?.spellcastingModifiers?.main; - const spellcastingModifier = this.partner?.system?.traits?.[partnerSpellcastingModifier]?.total; + 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; for (let levelKey in this.levelData.levelups) { @@ -107,15 +108,15 @@ export default class DhCompanion extends BaseDataActor { } break; case 'stress': - this.resources.stress.bonus += selection.value; + this.resources.stress.max += selection.value; break; case 'evasion': - this.evasion.bonus += selection.value; + this.evasion += selection.value; break; case 'experience': Object.keys(this.experiences).forEach(key => { const experience = this.experiences[key]; - experience.bonus += selection.value; + experience.value += selection.value; }); break; } @@ -124,17 +125,9 @@ export default class DhCompanion extends BaseDataActor { } prepareDerivedData() { - for (var experienceKey in this.experiences) { - var experience = this.experiences[experienceKey]; - experience.total = experience.value + experience.bonus; - } - if (this.partner) { this.partner.system.resources.hope.max += this.resources.hope; } - - this.resources.stress.maxTotal = this.resources.stress.max + this.resources.stress.bonus; - this.evasion.total = this.evasion.value + this.evasion.bonus; } async _preDelete() { diff --git a/module/data/actor/environment.mjs b/module/data/actor/environment.mjs index 76990e88..2db1f039 100644 --- a/module/data/actor/environment.mjs +++ b/module/data/actor/environment.mjs @@ -9,20 +9,21 @@ export default class DhEnvironment extends BaseDataActor { return foundry.utils.mergeObject(super.metadata, { label: 'TYPES.Actor.environment', type: 'environment', - settingSheet: DHEnvironmentSettings + settingSheet: DHEnvironmentSettings, + hasResistances: false }); } static defineSchema() { const fields = foundry.data.fields; return { + ...super.defineSchema(), tier: new fields.StringField({ required: true, choices: CONFIG.DH.GENERAL.tiers, initial: CONFIG.DH.GENERAL.tiers.tier1.id }), type: new fields.StringField({ choices: CONFIG.DH.ACTOR.environmentTypes }), - description: new fields.StringField(), impulses: new fields.StringField(), difficulty: new fields.NumberField({ required: true, initial: 11, integer: true }), potentialAdversaries: new fields.TypedObjectField( diff --git a/module/data/item/_module.mjs b/module/data/item/_module.mjs index a29d1595..bed18eb5 100644 --- a/module/data/item/_module.mjs +++ b/module/data/item/_module.mjs @@ -1,5 +1,6 @@ import DHAncestry from './ancestry.mjs'; import DHArmor from './armor.mjs'; +import DHAttachableItem from './attachableItem.mjs'; import DHClass from './class.mjs'; import DHCommunity from './community.mjs'; import DHConsumable from './consumable.mjs'; @@ -13,6 +14,7 @@ import DHBeastform from './beastform.mjs'; export { DHAncestry, DHArmor, + DHAttachableItem, DHClass, DHCommunity, DHConsumable, @@ -27,6 +29,7 @@ export { export const config = { ancestry: DHAncestry, armor: DHArmor, + attachableItem: DHAttachableItem, class: DHClass, community: DHCommunity, consumable: DHConsumable, diff --git a/module/data/item/armor.mjs b/module/data/item/armor.mjs index 696a95a2..7a8b06c0 100644 --- a/module/data/item/armor.mjs +++ b/module/data/item/armor.mjs @@ -1,8 +1,9 @@ -import BaseDataItem from './base.mjs'; +import AttachableItem from './attachableItem.mjs'; import ActionField from '../fields/actionField.mjs'; import { armorFeatures } from '../../config/itemConfig.mjs'; +import { actionsTypes } from '../action/_module.mjs'; -export default class DHArmor extends BaseDataItem { +export default class DHArmor extends AttachableItem { /** @inheritDoc */ static get metadata() { return foundry.utils.mergeObject(super.metadata, { @@ -44,6 +45,12 @@ export default class DHArmor extends BaseDataItem { }; } + get customActions() { + return this.actions.filter( + action => !this.armorFeatures.some(feature => feature.actionIds.includes(action.id)) + ); + } + async _preUpdate(changes, options, user) { const allowed = await super._preUpdate(changes, options, user); if (allowed === false) return false; diff --git a/module/data/item/attachableItem.mjs b/module/data/item/attachableItem.mjs new file mode 100644 index 00000000..2b0608eb --- /dev/null +++ b/module/data/item/attachableItem.mjs @@ -0,0 +1,152 @@ +import BaseDataItem from './base.mjs'; + +export default class AttachableItem extends BaseDataItem { + static defineSchema() { + const fields = foundry.data.fields; + return { + ...super.defineSchema(), + attached: new fields.ArrayField(new fields.DocumentUUIDField({ type: "Item", nullable: true })) + }; + } + + async _preUpdate(changes, options, user) { + const allowed = await super._preUpdate(changes, options, user); + if (allowed === false) return false; + + // Handle equipped status changes for attachment effects + if (changes.system?.equipped !== undefined && changes.system.equipped !== this.equipped) { + await this.#handleAttachmentEffectsOnEquipChange(changes.system.equipped); + } + } + + async #handleAttachmentEffectsOnEquipChange(newEquippedStatus) { + const actor = this.parent.parent?.type === 'character' ? this.parent.parent : this.parent.parent?.parent; + const parentType = this.parent.type; + + if (!actor || !this.attached?.length) { + return; + } + + if (newEquippedStatus) { + // Item is being equipped - add attachment effects + for (const attachedUuid of this.attached) { + const attachedItem = await fromUuid(attachedUuid); + if (attachedItem && attachedItem.effects.size > 0) { + await this.#copyAttachmentEffectsToActor({ + attachedItem, + attachedUuid, + parentType + }); + } + } + } else { + // Item is being unequipped - remove attachment effects + await this.#removeAllAttachmentEffects(parentType); + } + } + + async #copyAttachmentEffectsToActor({ attachedItem, attachedUuid, parentType }) { + const actor = this.parent.parent; + if (!actor || !attachedItem.effects.size > 0 || !this.equipped) { + return []; + } + + const effectsToCreate = []; + for (const effect of attachedItem.effects) { + const effectData = effect.toObject(); + effectData.origin = `${this.parent.uuid}:${attachedUuid}`; + + const attachmentSource = { + itemUuid: attachedUuid, + originalEffectId: effect.id + }; + attachmentSource[`${parentType}Uuid`] = this.parent.uuid; + + effectData.flags = { + ...effectData.flags, + [CONFIG.DH.id]: { + ...effectData.flags?.[CONFIG.DH.id], + [CONFIG.DH.FLAGS.itemAttachmentSource]: attachmentSource + } + }; + effectsToCreate.push(effectData); + } + + if (effectsToCreate.length > 0) { + return await actor.createEmbeddedDocuments('ActiveEffect', effectsToCreate); + } + + return []; + } + + async #removeAllAttachmentEffects(parentType) { + const actor = this.parent.parent; + if (!actor) return; + + const parentUuidProperty = `${parentType}Uuid`; + const effectsToRemove = actor.effects.filter(effect => { + const attachmentSource = effect.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.itemAttachmentSource); + return attachmentSource && attachmentSource[parentUuidProperty] === this.parent.uuid; + }); + + if (effectsToRemove.length > 0) { + await actor.deleteEmbeddedDocuments('ActiveEffect', effectsToRemove.map(e => e.id)); + } + } + + /** + * Public method for adding an attachment + */ + async addAttachment(droppedItem) { + const newUUID = droppedItem.uuid; + + if (this.attached.includes(newUUID)) { + ui.notifications.warn(`${droppedItem.name} is already attached to this ${this.parent.type}.`); + return; + } + + const updatedAttached = [...this.attached, newUUID]; + await this.parent.update({ + 'system.attached': updatedAttached + }); + + // Copy effects if equipped + if (this.equipped && droppedItem.effects.size > 0) { + await this.#copyAttachmentEffectsToActor({ + attachedItem: droppedItem, + attachedUuid: newUUID, + parentType: this.parent.type + }); + } + } + + /** + * Public method for removing an attachment + */ + async removeAttachment(attachedUuid) { + await this.parent.update({ + 'system.attached': this.attached.filter(uuid => uuid !== attachedUuid) + }); + + // Remove effects + await this.#removeAttachmentEffects(attachedUuid); + } + + async #removeAttachmentEffects(attachedUuid) { + const actor = this.parent.parent; + if (!actor) return; + + const parentType = this.parent.type; + const parentUuidProperty = `${parentType}Uuid`; + const effectsToRemove = actor.effects.filter(effect => { + const attachmentSource = effect.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.itemAttachmentSource); + return attachmentSource && + attachmentSource[parentUuidProperty] === this.parent.uuid && + attachmentSource.itemUuid === attachedUuid; + }); + + if (effectsToRemove.length > 0) { + await actor.deleteEmbeddedDocuments('ActiveEffect', effectsToRemove.map(e => e.id)); + } + } +} \ No newline at end of file diff --git a/module/data/item/subclass.mjs b/module/data/item/subclass.mjs index 3c47841d..265c2566 100644 --- a/module/data/item/subclass.mjs +++ b/module/data/item/subclass.mjs @@ -32,9 +32,9 @@ export default class DHSubclass extends BaseDataItem { get features() { return [ - { ...this.foundationFeature.toObject(), identifier: 'foundationFeature' }, - { ...this.specializationFeature.toObject(), identifier: 'specializationFeature' }, - { ...this.masteryFeature.toObject(), identifier: 'masteryFeature' } + { ...this.foundationFeature?.toObject(), identifier: 'foundationFeature' }, + { ...this.specializationFeature?.toObject(), identifier: 'specializationFeature' }, + { ...this.masteryFeature?.toObject(), identifier: 'masteryFeature' } ]; } diff --git a/module/data/item/weapon.mjs b/module/data/item/weapon.mjs index e1370395..82b4ba80 100644 --- a/module/data/item/weapon.mjs +++ b/module/data/item/weapon.mjs @@ -1,8 +1,8 @@ -import BaseDataItem from './base.mjs'; +import AttachableItem from './attachableItem.mjs'; import { actionsTypes } from '../action/_module.mjs'; import ActionField from '../fields/actionField.mjs'; -export default class DHWeapon extends BaseDataItem { +export default class DHWeapon extends AttachableItem { /** @inheritDoc */ static get metadata() { return foundry.utils.mergeObject(super.metadata, { @@ -37,7 +37,7 @@ export default class DHWeapon extends BaseDataItem { actionIds: new fields.ArrayField(new fields.StringField({ required: true })) }) ), - attack: new ActionField({ + attack: new ActionField({ initial: { name: 'Attack', img: 'icons/skills/melee/blood-slash-foam-red.webp', @@ -56,6 +56,7 @@ export default class DHWeapon extends BaseDataItem { damage: { parts: [ { + type: ['physical'], value: { multiplier: 'prof', dice: 'd8' @@ -73,6 +74,12 @@ export default class DHWeapon extends BaseDataItem { return [this.attack, ...this.actions]; } + get customActions() { + return this.actions.filter( + action => !this.weaponFeatures.some(feature => feature.actionIds.includes(action.id)) + ); + } + async _preUpdate(changes, options, user) { const allowed = await super._preUpdate(changes, options, user); if (allowed === false) return false; diff --git a/module/data/settings/Appearance.mjs b/module/data/settings/Appearance.mjs index 8b04f558..d8b4c687 100644 --- a/module/data/settings/Appearance.mjs +++ b/module/data/settings/Appearance.mjs @@ -40,6 +40,10 @@ export default class DhAppearance extends foundry.abstract.DataModel { outline: new fields.ColorField({ required: true, initial: '#ffffff' }), edge: new fields.ColorField({ required: true, initial: '#000000' }) }) + }), + showGenericStatusEffects: new fields.BooleanField({ + initial: true, + label: 'DAGGERHEART.SETTINGS.Appearance.FIELDS.showGenericStatusEffects.label' }) }; } diff --git a/module/data/settings/RangeMeasurement.mjs b/module/data/settings/RangeMeasurement.mjs index 71fb7787..552963f0 100644 --- a/module/data/settings/RangeMeasurement.mjs +++ b/module/data/settings/RangeMeasurement.mjs @@ -2,7 +2,7 @@ export default class DhRangeMeasurement extends foundry.abstract.DataModel { static defineSchema() { const fields = foundry.data.fields; return { - enabled: new fields.BooleanField({ required: true, initial: false, label: 'DAGGERHEART.GENERAL.enabled' }), + enabled: new fields.BooleanField({ required: true, initial: true, label: 'DAGGERHEART.GENERAL.enabled' }), melee: new fields.NumberField({ required: true, initial: 5, label: 'DAGGERHEART.CONFIG.Range.melee.name' }), veryClose: new fields.NumberField({ required: true, diff --git a/module/dice/d20Roll.mjs b/module/dice/d20Roll.mjs index c344231f..c9e9d428 100644 --- a/module/dice/d20Roll.mjs +++ b/module/dice/d20Roll.mjs @@ -1,5 +1,4 @@ import D20RollDialog from '../applications/dialogs/d20RollDialog.mjs'; -import { setDiceSoNiceForDualityRoll } from '../helpers/utils.mjs'; import DHRoll from './dhRoll.mjs'; export default class D20Roll extends DHRoll { @@ -98,7 +97,7 @@ export default class D20Roll extends DHRoll { if (this.options.data.experiences?.[m]) this.options.roll.modifiers.push({ label: this.options.data.experiences[m].name, - value: this.options.data.experiences[m].total ?? this.options.data.experiences[m].value + value: this.options.data.experiences[m].value }); }); @@ -137,12 +136,7 @@ export default class D20Roll extends DHRoll { static async buildEvaluate(roll, config = {}, message = {}) { if (config.evaluate !== false) await roll.evaluate(); - const advantageState = - config.roll.advantage == this.ADV_MODE.ADVANTAGE - ? true - : config.roll.advantage == this.ADV_MODE.DISADVANTAGE - ? false - : null; + this.postEvaluate(roll, config); } diff --git a/module/dice/damageRoll.mjs b/module/dice/damageRoll.mjs index d9c4a61e..2182d3d0 100644 --- a/module/dice/damageRoll.mjs +++ b/module/dice/damageRoll.mjs @@ -14,6 +14,10 @@ export default class DamageRoll extends DHRoll { super.postEvaluate(roll, config); config.roll.type = config.type; config.roll.modifierTotal = this.calculateTotalModifiers(roll); + } + + static async buildPost(roll, config, message) { + await super.buildPost(roll, config, message); if (config.source?.message) { const chatMessage = ui.chat.collection.get(config.source.message); chatMessage.update({ 'system.damage': config }); diff --git a/module/dice/dhRoll.mjs b/module/dice/dhRoll.mjs index 13246ac9..87c9401e 100644 --- a/module/dice/dhRoll.mjs +++ b/module/dice/dhRoll.mjs @@ -56,8 +56,8 @@ export default class DHRoll extends Roll { // Create Chat Message if (config.source?.message) { + if(game.modules.get('dice-so-nice')?.active) await game.dice3d.showForRoll(roll, game.user, true); } else { - const messageData = {}; config.message = await this.toMessage(roll, config); } } @@ -154,7 +154,7 @@ export const registerRollDiceHooks = () => { if (updates.length) actor.modifyResource(updates); - if (!config.roll.hasOwnProperty('success') && !config.targets.length) return; + 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; diff --git a/module/dice/dualityRoll.mjs b/module/dice/dualityRoll.mjs index 4d0e99a3..1044b93a 100644 --- a/module/dice/dualityRoll.mjs +++ b/module/dice/dualityRoll.mjs @@ -1,5 +1,6 @@ import D20RollDialog from '../applications/dialogs/d20RollDialog.mjs'; import D20Roll from './d20Roll.mjs'; +import { setDiceSoNiceForDualityRoll } from '../helpers/utils.mjs'; export default class DualityRoll extends D20Roll { _advantageFaces = 6; @@ -80,7 +81,6 @@ export default class DualityRoll extends D20Roll { } static getHooks(hooks) { - return [...(hooks ?? []), 'Duality']; } @@ -123,7 +123,7 @@ export default class DualityRoll extends D20Roll { 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}.total`, this.data) + value: Roll.replaceFormulaData(`@traits.${this.options.roll.trait}.value`, this.data) }); } @@ -142,5 +142,7 @@ export default class DualityRoll extends D20Roll { total: roll.dHope.total + roll.dFear.total, label: roll.totalLabel }; + + setDiceSoNiceForDualityRoll(roll, config.roll.advantage.type); } } diff --git a/module/documents/activeEffect.mjs b/module/documents/activeEffect.mjs index 1abc2d17..03ac73bc 100644 --- a/module/documents/activeEffect.mjs +++ b/module/documents/activeEffect.mjs @@ -1,16 +1,42 @@ export default class DhActiveEffect extends ActiveEffect { get isSuppressed() { - if (['weapon', 'armor'].includes(this.parent.type)) { + // If this is a copied effect from an attachment, never suppress it + // (These effects have attachmentSource metadata) + if (this.flags?.daggerheart?.attachmentSource) { + return false; + } + + // Then apply the standard suppression rules + if (['weapon', 'armor'].includes(this.parent?.type)) { return !this.parent.system.equipped; } - if (this.parent.type === 'domainCard') { + if (this.parent?.type === 'domainCard') { return this.parent.system.inVault; } return super.isSuppressed; } + /** + * Check if the parent item is currently attached to another item + * @returns {boolean} + */ + get isAttached() { + if (!this.parent || !this.parent.parent) return false; + + // Check if this item's UUID is in any actor's armor or weapon attachment lists + const actor = this.parent.parent; + if (!actor || !actor.items) return false; + + return actor.items.some(item => { + return (item.type === 'armor' || item.type === 'weapon') && + item.system?.attached && + Array.isArray(item.system.attached) && + item.system.attached.includes(this.parent.uuid); + }); + } + async _preCreate(data, options, user) { const update = {}; if (!data.img) { diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index aba0e0fa..d8d75461 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -370,99 +370,19 @@ export default class DhpActor extends Actor { } getRollData() { - return this.system; - } - - formatRollModifier(roll) { - const modifier = roll.modifier !== null ? Number.parseInt(roll.modifier) : null; - return modifier !== null - ? [ - { - value: modifier, - label: roll.label - ? modifier >= 0 - ? `${roll.label} +${modifier}` - : `${roll.label} ${modifier}` - : null, - title: roll.label - } - ] - : []; - } - - async damageRoll(title, damage, targets, shiftKey) { - let rollString = damage.value; - let bonusDamage = damage.bonusDamage?.filter(x => x.initiallySelected) ?? []; - if (!shiftKey) { - const dialogClosed = new Promise((resolve, _) => { - new DamageSelectionDialog(rollString, bonusDamage, resolve).render(true); - }); - const result = await dialogClosed; - bonusDamage = result.bonusDamage; - rollString = result.rollString; - - const automateHope = await game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation.Hope); - if (automateHope && result.hopeUsed) { - await this.update({ - 'system.resources.hope.value': this.system.resources.hope.value - result.hopeUsed - }); - } - } - - const roll = new Roll(rollString); - let rollResult = await roll.evaluate(); - - const dice = []; - const modifiers = []; - for (var i = 0; i < rollResult.terms.length; i++) { - const term = rollResult.terms[i]; - if (term.faces) { - dice.push({ - type: `d${term.faces}`, - rolls: term.results.map(x => x.result), - total: term.results.reduce((acc, x) => acc + x.result, 0) - }); - } else if (term.operator) { - } else if (term.number) { - const operator = i === 0 ? '' : rollResult.terms[i - 1].operator; - modifiers.push({ value: term.number, operator: operator }); - } - } - - const cls = getDocumentClass('ChatMessage'); - const systemData = { - title: game.i18n.format('DAGGERHEART.UI.Chat.damageRoll.title', { damage: title }), - roll: rollString, - damage: { - total: rollResult.total, - type: damage.type - }, - dice: dice, - modifiers: modifiers, - targets: targets - }; - const msg = new cls({ - type: 'damageRoll', - user: game.user.id, - sound: CONFIG.sounds.dice, - system: systemData, - content: await foundry.applications.handlebars.renderTemplate( - 'systems/daggerheart/templates/ui/chat/damage-roll.hbs', - systemData - ), - rolls: [roll] - }); - - cls.create(msg.toObject()); + const rollData = super.getRollData(); + rollData.prof = this.system.proficiency ?? 1; + rollData.cast = this.system.spellcast ?? 1; + return rollData; } #canReduceDamage(hpDamage, type) { - const availableStress = this.system.resources.stress.maxTotal - this.system.resources.stress.value; + const availableStress = this.system.resources.stress.max - this.system.resources.stress.value; const canUseArmor = this.system.armor && this.system.armor.system.marks.value < this.system.armorScore && - this.system.armorApplicableDamageTypes[type]; + type.every(t => this.system.armorApplicableDamageTypes[t] === true); const canUseStress = Object.keys(this.system.rules.damageReduction.stressDamageReduction).reduce((acc, x) => { const rule = this.system.rules.damageReduction.stressDamageReduction[x]; if (damageKeyToNumber(x) <= hpDamage) return acc || (rule.enabled && availableStress >= rule.cost); @@ -480,11 +400,9 @@ export default class DhpActor extends Actor { return; } - const flatReduction = this.system.bonuses.damageReduction[type]; - const damage = Math.max(baseDamage - (flatReduction ?? 0), 0); - const hpDamage = this.convertDamageToThreshold(damage); + type = !Array.isArray(type) ? [type] : type; - if (Hooks.call(`${CONFIG.DH.id}.postDamageTreshold`, this, hpDamage, damage, type) === false) return null; + const hpDamage = this.calculateDamage(baseDamage, type); if (!hpDamage) return; @@ -511,6 +429,35 @@ export default class DhpActor extends Actor { if (Hooks.call(`${CONFIG.DH.id}.postTakeDamage`, this, damage, type) === false) return null; } + calculateDamage(baseDamage, type) { + if (Hooks.call(`${CONFIG.DH.id}.preCalculateDamage`, this, baseDamage, type) === false) return null; + + /* if(this.system.resistance[type]?.immunity) return 0; + if(this.system.resistance[type]?.resistance) baseDamage = Math.ceil(baseDamage / 2); */ + if(this.canResist(type, 'immunity')) return 0; + if(this.canResist(type, 'resistance')) baseDamage = Math.ceil(baseDamage / 2); + + // const flatReduction = this.system.resistance[type].reduction; + const flatReduction = this.getDamageTypeReduction(type); + const damage = Math.max(baseDamage - (flatReduction ?? 0), 0); + const hpDamage = this.convertDamageToThreshold(damage); + + if (Hooks.call(`${CONFIG.DH.id}.postCalculateDamage`, this, baseDamage, type) === false) return null; + + return hpDamage; + } + + canResist(type, resistance) { + if(!type) return 0; + return type.every(t => this.system.resistance[t]?.[resistance] === true); + } + + getDamageTypeReduction(type) { + if(!type) return 0; + const reduction = Object.entries(this.system.resistance).reduce((a, [index, value]) => type.includes(index) ? Math.min(value.reduction, a) : a, Infinity); + return reduction === Infinity ? 0 : reduction; + } + async takeHealing(resources) { resources.forEach(r => (r.value *= -1)); await this.modifyResource(resources); @@ -538,7 +485,7 @@ export default class DhpActor extends Actor { updates.actor.resources[`system.resources.${r.type}.value`] = Math.max( Math.min( this.system.resources[r.type].value + r.value, - this.system.resources[r.type].maxTotal ?? this.system.resources[r.type].max + this.system.resources[r.type].max ), 0 ); @@ -553,18 +500,6 @@ export default class DhpActor extends Actor { u.resources, u.target.uuid ); - /* if (game.user.isGM) { - await u.target.update(u.resources); - } else { - await game.socket.emit(`system.${CONFIG.DH.id}`, { - action: socketEvent.GMUpdate, - data: { - action: GMUpdateEvent.UpdateDocument, - uuid: u.target.uuid, - update: u.resources - } - }); - } */ } }); } @@ -582,7 +517,7 @@ export default class DhpActor extends Actor { convertStressDamageToHP(resources) { const stressDamage = resources.find(r => r.type === 'stress'), newValue = this.system.resources.stress.value + stressDamage.value; - if (newValue <= this.system.resources.stress.maxTotal) return; + if (newValue <= this.system.resources.stress.max) return; const hpDamage = resources.find(r => r.type === 'hitPoints'); if (hpDamage) hpDamage.value++; else diff --git a/module/documents/item.mjs b/module/documents/item.mjs index db34fa49..6c3732db 100644 --- a/module/documents/item.mjs +++ b/module/documents/item.mjs @@ -80,12 +80,16 @@ export default class DHItem extends foundry.documents.Item { async selectActionDialog(prevEvent) { const content = await foundry.applications.handlebars.renderTemplate( 'systems/daggerheart/templates/dialogs/actionSelect.hbs', - { actions: this.system.actionsList } + { + actions: this.system.actionsList, + itemName: this.name + } ), - title = 'Select Action'; + title = game.i18n.localize('DAGGERHEART.CONFIG.SelectAction.selectAction'); return foundry.applications.api.DialogV2.prompt({ window: { title }, + classes: ['daggerheart', 'dh-style'], content, ok: { label: title, diff --git a/module/documents/tooltipManager.mjs b/module/documents/tooltipManager.mjs index 2e660cff..d9444207 100644 --- a/module/documents/tooltipManager.mjs +++ b/module/documents/tooltipManager.mjs @@ -2,15 +2,33 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti async activate(element, options = {}) { let html = options.html; if (element.dataset.tooltip?.startsWith('#item#')) { - const item = await foundry.utils.fromUuid(element.dataset.tooltip.slice(6)); + const splitValues = element.dataset.tooltip.slice(6).split('#action#'); + const itemUuid = splitValues[0]; + const actionId = splitValues.length > 1 ? splitValues[1] : null; + + const baseItem = await foundry.utils.fromUuid(itemUuid); + const item = actionId ? baseItem.system.actions.find(x => x.id === actionId) : baseItem; if (item) { + const type = actionId ? 'action' : item.type; html = await foundry.applications.handlebars.renderTemplate( - `systems/daggerheart/templates/ui/tooltip/${item.type}.hbs`, - item + `systems/daggerheart/templates/ui/tooltip/${type}.hbs`, + { + item: item, + config: CONFIG.DH + } ); + + this.tooltip.innerHTML = html; + options.direction = this._determineItemTooltipDirection(element); } } super.activate(element, { ...options, html: html }); } + + _determineItemTooltipDirection(element) { + const pos = element.getBoundingClientRect(); + const dirs = this.constructor.TOOLTIP_DIRECTIONS; + return dirs[pos.x - this.tooltip.offsetWidth < 0 ? 'DOWN' : 'LEFT']; + } } diff --git a/module/helpers/handlebarsHelper.mjs b/module/helpers/handlebarsHelper.mjs index 33bb16af..8b1269fd 100644 --- a/module/helpers/handlebarsHelper.mjs +++ b/module/helpers/handlebarsHelper.mjs @@ -1,35 +1,48 @@ -import { getWidthOfText } from './utils.mjs'; - export default class RegisterHandlebarsHelpers { static registerHelpers() { Handlebars.registerHelper({ - times: this.times, add: this.add, - subtract: this.subtract, includes: this.includes, + times: this.times, + damageFormula: this.damageFormula, + damageSymbols: this.damageSymbols, + tertiary: this.tertiary }); } - static times(nr, block) { - var accum = ''; - for (var i = 0; i < nr; ++i) accum += block.fn(i); - return accum; - } - static add(a, b) { const aNum = Number.parseInt(a); const bNum = Number.parseInt(b); return (Number.isNaN(aNum) ? 0 : aNum) + (Number.isNaN(bNum) ? 0 : bNum); } - static subtract(a, b) { - const aNum = Number.parseInt(a); - const bNum = Number.parseInt(b); - return (Number.isNaN(aNum) ? 0 : aNum) - (Number.isNaN(bNum) ? 0 : bNum); - } - static includes(list, item) { return list.includes(item); } + static times(nr, block) { + var accum = ''; + for (var i = 0; i < nr; ++i) accum += block.fn(i); + return accum; + } + + static damageFormula(attack, actor) { + const traitTotal = actor.system.traits?.[attack.roll.trait]?.value; + const instances = [ + attack.damage.parts.map(x => Roll.replaceFormulaData(x.value.getFormula(), actor)).join(' + '), + traitTotal + ].filter(x => x); + + return instances.join(traitTotal > 0 ? ' + ' : ' - '); + } + + static damageSymbols(damageParts) { + const symbols = new Set(); + damageParts.forEach(part => symbols.add(...CONFIG.DH.GENERAL.damageTypes[part.type].icon)); + return new Handlebars.SafeString(Array.from(symbols).map(symbol => ``)); + } + + static tertiary(a, b) { + return a ?? b; + } } diff --git a/module/helpers/utils.mjs b/module/helpers/utils.mjs index 3030bc08..425bea79 100644 --- a/module/helpers/utils.mjs +++ b/module/helpers/utils.mjs @@ -126,12 +126,10 @@ export const setDiceSoNiceForDualityRoll = (rollResult, advantageState) => { const diceSoNicePresets = getDiceSoNicePresets(); rollResult.dice[0].options = { appearance: diceSoNicePresets.hope }; rollResult.dice[1].options = { appearance: diceSoNicePresets.fear }; //diceSoNicePresets.fear; - if (rollResult.dice[2]) { - if (advantageState === true) { - rollResult.dice[2].options = { appearance: diceSoNicePresets.advantage }; - } else if (advantageState === false) { - rollResult.dice[2].options = { appearance: diceSoNicePresets.disadvantage }; - } + if (rollResult.dice[2] && advantageState) { + rollResult.dice[2].options = { + appearance: advantageState === 1 ? diceSoNicePresets.advantage : diceSoNicePresets.disadvantage + }; } }; @@ -238,16 +236,7 @@ Roll.replaceFormulaData = function (formula, data = {}, { missing, warn = false }; export const getDamageKey = damage => { - switch (damage) { - case 3: - return 'severe'; - case 2: - return 'major'; - case 1: - return 'minor'; - case 0: - return 'none'; - } + return ['none', 'minor', 'major', 'severe'][damage]; }; export const getDamageLabel = damage => { @@ -255,16 +244,12 @@ export const getDamageLabel = damage => { }; export const damageKeyToNumber = key => { - switch (key) { - case 'severe': - return 3; - case 'major': - return 2; - case 'minor': - return 1; - case 'none': - return 0; - } + return { + 'none': 0, + 'minor': 1, + 'major': 2, + 'severe': 3 + }[key]; }; export default function constructHTMLButton({ diff --git a/module/systemRegistration/handlebars.mjs b/module/systemRegistration/handlebars.mjs index f2cc2e82..82eda4b5 100644 --- a/module/systemRegistration/handlebars.mjs +++ b/module/systemRegistration/handlebars.mjs @@ -26,6 +26,7 @@ export const preloadHandlebarsTemplates = async function () { 'systems/daggerheart/templates/actionTypes/beastform.hbs', 'systems/daggerheart/templates/settings/components/settings-item-line.hbs', 'systems/daggerheart/templates/ui/chat/parts/damage-chat.hbs', - 'systems/daggerheart/templates/ui/chat/parts/target-chat.hbs' + 'systems/daggerheart/templates/ui/chat/parts/target-chat.hbs', + 'systems/daggerheart/templates/ui/tooltip/parts/tooltipTags.hbs' ]); }; diff --git a/module/systemRegistration/settings.mjs b/module/systemRegistration/settings.mjs index a4ed05c8..fea12acd 100644 --- a/module/systemRegistration/settings.mjs +++ b/module/systemRegistration/settings.mjs @@ -60,7 +60,7 @@ const registerMenuSettings = () => { }); game.settings.register(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.RangeMeasurement, { - scope: 'client', + scope: 'world', config: false, type: DhRangeMeasurement }); diff --git a/styles/daggerheart.less b/styles/daggerheart.less index 86b504b2..c2127681 100755 --- a/styles/daggerheart.less +++ b/styles/daggerheart.less @@ -3,6 +3,8 @@ @import './less/dialog/index.less'; +@import './less//hud/index.less'; + @import './less/utils/colors.less'; @import './less/utils/fonts.less'; @@ -10,4 +12,6 @@ @import './less/ui/index.less'; +@import './less/ux/index.less'; + @import '../node_modules/@yaireo/tagify/dist/tagify.css'; diff --git a/styles/less/dialog/actions/action-list.less b/styles/less/dialog/actions/action-list.less new file mode 100644 index 00000000..800f7d8e --- /dev/null +++ b/styles/less/dialog/actions/action-list.less @@ -0,0 +1,19 @@ +@import '../../utils/fonts.less'; + +.application.daggerheart.dh-style { + .actions-list { + display: flex; + flex-direction: column; + gap: 10px; + + .action-item { + display: flex; + align-items: center; + gap: 5px; + + .label { + font-family: @font-body; + } + } + } +} diff --git a/styles/less/dialog/damage-selection/sheet.less b/styles/less/dialog/damage-selection/sheet.less new file mode 100644 index 00000000..43e4f4d2 --- /dev/null +++ b/styles/less/dialog/damage-selection/sheet.less @@ -0,0 +1,20 @@ +@import '../../utils/colors.less'; + +.daggerheart.dialog.dh-style.views.damage-selection { + .damage-section-container { + display: flex; + flex-direction: column; + gap: 12px; + + input[type='text'], + input[type='number'] { + color: light-dark(@dark, @beige); + outline: 2px solid transparent; + transition: all 0.3s ease; + + &:hover { + outline: 2px solid light-dark(@dark, @beige); + } + } + } +} diff --git a/styles/less/dialog/dice-roll/roll-selection.less b/styles/less/dialog/dice-roll/roll-selection.less index b2f05dc2..575b7ce9 100644 --- a/styles/less/dialog/dice-roll/roll-selection.less +++ b/styles/less/dialog/dice-roll/roll-selection.less @@ -114,15 +114,5 @@ } } } - - .formula-label { - font-family: @font-body; - font-style: normal; - font-weight: 500; - font-size: 14px; - line-height: 17px; - - color: light-dark(@dark, @beige); - } } } diff --git a/styles/less/dialog/index.less b/styles/less/dialog/index.less index 40280270..545ce2e1 100644 --- a/styles/less/dialog/index.less +++ b/styles/less/dialog/index.less @@ -4,6 +4,10 @@ @import './level-up/summary-container.less'; @import './level-up/tiers-container.less'; +@import './actions/action-list.less'; + +@import './damage-selection/sheet.less'; + @import './downtime/downtime-container.less'; @import './beastform/beastform-container.less'; diff --git a/styles/less/global/dialog.less b/styles/less/global/dialog.less index 3856facb..11a4eee9 100644 --- a/styles/less/global/dialog.less +++ b/styles/less/global/dialog.less @@ -1,5 +1,6 @@ @import '../utils/colors.less'; @import '../utils/fonts.less'; +@import '../utils/mixin.less'; .appTheme({ &.dialog { @@ -40,4 +41,19 @@ } } } + + .submit-btn { + width: 100%; + height: 38px; + } + + .formula-label { + font-family: @font-body; + font-style: normal; + font-weight: 500; + font-size: 14px; + line-height: 17px; + + color: light-dark(@dark, @beige); + } } diff --git a/styles/less/global/elements.less b/styles/less/global/elements.less index 03772bfb..ab519a1c 100755 --- a/styles/less/global/elements.less +++ b/styles/less/global/elements.less @@ -35,15 +35,16 @@ } } - input[type='checkbox'] { + input[type='checkbox'], + input[type='radio'] { &:checked::after { - color: light-dark(@dark, @golden); + color: light-dark(@dark-40, @golden); } &:checked::before { - color: light-dark(transparent, @dark-blue); + color: light-dark(@dark-40, @golden-40); } &::before { - color: light-dark(@dark, @beige); + color: light-dark(@dark-40, @golden-40); } } @@ -103,6 +104,40 @@ } } + multi-select { + position: relative; + height: 34px; + .tags { + justify-content: flex-start; + margin: 5px; + height: inherit; + .tag { + box-shadow: 0 0 0 1.1em #E5E5E5 inset; + vertical-align: top; + box-sizing: border-box; + max-width: 100%; + padding: 0.3em 0 0.3em 0.5em; + color: black; + border-radius: 3px; + white-space: nowrap; + transition: .13s ease-out; + height: 22px; + font-size: .9rem; + gap: 0.5em; + z-index: 1; + .remove { + font-size: 10px; + margin-inline: auto 4.6666666667px; + } + } + } + select { + position: absolute; + height: inherit; + outline: initial; + } + } + p { margin: 0; } @@ -319,6 +354,17 @@ transform: translateY(-20px); transform-origin: top; } + + .item-buttons { + grid-column: span 3; + display: flex; + gap: 8px; + flex-wrap: wrap; + + button { + white-space: nowrap; + } + } } .application.setting.dh-style { diff --git a/styles/less/global/index.less b/styles/less/global/index.less index 0559c7ff..932e48ab 100644 --- a/styles/less/global/index.less +++ b/styles/less/global/index.less @@ -12,3 +12,4 @@ @import './inventory-fieldset-items.less'; @import './prose-mirror.less'; @import './filter-menu.less'; +@import './tab-attachments.less'; diff --git a/styles/less/global/sheet.less b/styles/less/global/sheet.less index 1e44d7a0..1a00239a 100755 --- a/styles/less/global/sheet.less +++ b/styles/less/global/sheet.less @@ -4,8 +4,8 @@ // Theme handling .appTheme({ - background: @semi-transparent-dark-blue; - backdrop-filter: blur(9px); + background: @dark-blue-60; + backdrop-filter: blur(10px); }, { background: url('../assets/parchments/dh-parchment-light.png') no-repeat center; }); @@ -44,6 +44,8 @@ top: -36px; min-height: -webkit-fill-available; transition: opacity 0.3s ease; + padding-bottom: 20px; + margin-bottom: -36px; .tab { padding: 0 10px; diff --git a/styles/less/global/tab-attachments.less b/styles/less/global/tab-attachments.less new file mode 100644 index 00000000..c283269e --- /dev/null +++ b/styles/less/global/tab-attachments.less @@ -0,0 +1,7 @@ +.daggerheart.dh-style { + .tab.attachments { + .attached-items { + width: 100%; + } + } +} \ No newline at end of file diff --git a/styles/less/hud/index.less b/styles/less/hud/index.less new file mode 100644 index 00000000..459f8fd7 --- /dev/null +++ b/styles/less/hud/index.less @@ -0,0 +1 @@ +@import './token-hud/token-hud.less'; diff --git a/styles/less/hud/token-hud/token-hud.less b/styles/less/hud/token-hud/token-hud.less new file mode 100644 index 00000000..7d231e8c --- /dev/null +++ b/styles/less/hud/token-hud/token-hud.less @@ -0,0 +1,10 @@ +.daggerheart.placeable-hud { + .col.right { + .palette { + .palette-category-title { + grid-column: span var(--effect-columns); + font-weight: bold; + } + } + } +} diff --git a/styles/less/sheets-settings/adversary-settings/experiences.less b/styles/less/sheets-settings/adversary-settings/experiences.less index 89a7c2d9..05595ed4 100644 --- a/styles/less/sheets-settings/adversary-settings/experiences.less +++ b/styles/less/sheets-settings/adversary-settings/experiences.less @@ -5,6 +5,7 @@ .tab.experiences { .add-experience-btn { width: 100%; + height: 38px; margin-bottom: 12px; } diff --git a/styles/less/sheets-settings/adversary-settings/features.less b/styles/less/sheets-settings/adversary-settings/features.less index 5798bfa9..037a08ea 100644 --- a/styles/less/sheets-settings/adversary-settings/features.less +++ b/styles/less/sheets-settings/adversary-settings/features.less @@ -10,6 +10,7 @@ .add-feature-btn { width: 100%; + height: 38px; margin-bottom: 12px; } diff --git a/styles/less/sheets-settings/environment-settings/adversaries.less b/styles/less/sheets-settings/environment-settings/adversaries.less index ba8cc8e4..8dc12c87 100644 --- a/styles/less/sheets-settings/environment-settings/adversaries.less +++ b/styles/less/sheets-settings/environment-settings/adversaries.less @@ -10,6 +10,7 @@ .add-action-btn { width: 100%; + height: 38px; margin-bottom: 12px; } diff --git a/styles/less/sheets-settings/environment-settings/features.less b/styles/less/sheets-settings/environment-settings/features.less index e4bb039f..f2a9583a 100644 --- a/styles/less/sheets-settings/environment-settings/features.less +++ b/styles/less/sheets-settings/environment-settings/features.less @@ -10,6 +10,7 @@ .add-feature-btn { width: 100%; + height: 38px; margin-bottom: 12px; } diff --git a/styles/less/sheets/actors/adversary/sheet.less b/styles/less/sheets/actors/adversary/sheet.less index af7918c6..286351c9 100644 --- a/styles/less/sheets/actors/adversary/sheet.less +++ b/styles/less/sheets/actors/adversary/sheet.less @@ -9,6 +9,7 @@ gap: 15px 0; height: 100%; width: 100%; + padding-bottom: 0; .adversary-sidebar-sheet { grid-row: 1 / span 2; diff --git a/styles/less/sheets/actors/adversary/sidebar.less b/styles/less/sheets/actors/adversary/sidebar.less index e50ba0c9..c1bd1856 100644 --- a/styles/less/sheets/actors/adversary/sidebar.less +++ b/styles/less/sheets/actors/adversary/sidebar.less @@ -110,10 +110,11 @@ justify-content: space-evenly; .status-bar { + display: flex; + justify-content: center; position: relative; width: 100px; height: 40px; - justify-items: center; .status-label { position: relative; diff --git a/styles/less/sheets/actors/character/sheet.less b/styles/less/sheets/actors/character/sheet.less index f2c9bb1a..8afd7404 100644 --- a/styles/less/sheets/actors/character/sheet.less +++ b/styles/less/sheets/actors/character/sheet.less @@ -9,6 +9,7 @@ gap: 15px 0; height: 100%; width: 100%; + padding-bottom: 0; overflow: auto; .character-sidebar-sheet { diff --git a/styles/less/sheets/actors/character/sidebar.less b/styles/less/sheets/actors/character/sidebar.less index d6ceab46..f46a9628 100644 --- a/styles/less/sheets/actors/character/sidebar.less +++ b/styles/less/sheets/actors/character/sidebar.less @@ -70,10 +70,11 @@ justify-content: space-evenly; .status-bar { + display: flex; + justify-content: center; position: relative; width: 100px; height: 40px; - justify-items: center; .status-label { position: relative; diff --git a/styles/less/sheets/actors/companion/header.less b/styles/less/sheets/actors/companion/header.less index fac32ea5..832a6050 100644 --- a/styles/less/sheets/actors/companion/header.less +++ b/styles/less/sheets/actors/companion/header.less @@ -45,7 +45,9 @@ justify-content: center; .status-number { - justify-items: center; + display: flex; + flex-direction: column; + align-items: center; .status-value { position: relative; @@ -85,6 +87,8 @@ } .status-bar { + display: flex; + justify-content: center; position: relative; width: 100px; height: 40px; diff --git a/styles/less/utils/colors.less b/styles/less/utils/colors.less index 3e72b743..622480a7 100755 --- a/styles/less/utils/colors.less +++ b/styles/less/utils/colors.less @@ -25,10 +25,12 @@ @dark-blue-10: #18162e10; @dark-blue-40: #18162e40; @dark-blue-50: #18162e50; +@dark-blue-60: #18162e60; @semi-transparent-dark-blue: rgba(24, 22, 46, 0.33); @dark: #222; @dark-15: #22222215; +@dark-40: #22222240; @deep-black: #0e0d15; diff --git a/styles/less/ux/index.less b/styles/less/ux/index.less new file mode 100644 index 00000000..ff645288 --- /dev/null +++ b/styles/less/ux/index.less @@ -0,0 +1 @@ +@import './tooltip/tooltip.less'; diff --git a/styles/less/ux/tooltip/tooltip.less b/styles/less/ux/tooltip/tooltip.less new file mode 100644 index 00000000..38502d09 --- /dev/null +++ b/styles/less/ux/tooltip/tooltip.less @@ -0,0 +1,106 @@ +.daggerheart.dh-style.tooltip { + display: flex; + flex-direction: column; + align-items: center; + gap: 4px; + + .tooltip-title { + margin: 0; + text-align: center; + } + + .tooltip-image { + height: 180px; + width: 180px; + } + + .tooltip-description { + font-style: italic; + } + + .tooltip-sub-title { + margin: 0; + color: light-dark(@dark-blue, @beige); + } + + .tooltip-information-section { + width: 100%; + display: grid; + grid-template-columns: 1fr 1fr; + gap: 4px; + + &.triple { + grid-template-columns: 1fr 1fr 1fr; + } + + &.border { + border: 1px solid light-dark(@dark-blue, @golden); + border-radius: 6px; + padding: 2px; + } + + .tooltip-information { + display: flex; + flex-direction: column; + align-items: center; + gap: 2px; + + &.full-width { + grid-column: span 2; + } + + label { + font-weight: bold; + } + + label, + div { + white-space: nowrap; + } + } + } + + .tooltip-tags { + width: 100%; + display: flex; + flex-direction: column; + gap: 4px; + + .tooltip-tag { + width: 100%; + display: grid; + grid-template-columns: 80px 1fr; + align-items: start; + gap: 8px; + padding: 4px; + border: 1px solid light-dark(@dark-blue, @golden); + border-radius: 6px; + + .tooltip-tag-label-container { + display: flex; + align-items: center; + flex-direction: column; + gap: 2px; + + .tooltip-tag-image { + width: 40px; + height: 40px; + } + } + + .tooltip-tag-label { + font-weight: bold; + text-align: center; + } + + .tooltip-tag-description { + display: flex; + flex-wrap: wrap; + } + } + } + + .spaced { + margin-bottom: 4px; + } +} diff --git a/templates/actionTypes/actionType.hbs b/templates/actionTypes/actionType.hbs index fdffaabe..1cd912e9 100644 --- a/templates/actionTypes/actionType.hbs +++ b/templates/actionTypes/actionType.hbs @@ -1,13 +1,12 @@
-