diff --git a/lang/en.json b/lang/en.json index ee7964cb..efc83968 100755 --- a/lang/en.json +++ b/lang/en.json @@ -402,6 +402,7 @@ "default": "Default Ownership" } }, + "CONFIG": { "ActionType": { "passive": "Passive", @@ -690,6 +691,10 @@ "name": "Dice Set" } }, + "SelectAction": { + "selectType": "Select Action Type", + "selectAction": "Select Action" + }, "Traits": { "agility": { "name": "Agility", @@ -967,6 +972,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": { @@ -996,6 +1005,11 @@ "minor": "Minor", "none": "None" }, + "DamageResistance": { + "none": "None", + "resistance": "Resistance", + "immunity": "Immunity" + }, "DamageThresholds": { "title": "Damage Thresholds", "minor": "Minor", @@ -1097,7 +1111,8 @@ "optional": "Optional", "recovery": "Recovery", "setup": "Setup", - "equipment": "Equipment" + "equipment": "Equipment", + "attachments": "Attachments" }, "Tiers": { "singular": "Tier", 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..0a0ebce1 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, 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/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 ae7ffbf9..5f0199f3 100644 --- a/module/applications/sheets/actors/character.mjs +++ b/module/applications/sheets/actors/character.mjs @@ -30,7 +30,12 @@ export default class CharacterSheet extends DHBaseActorSheet { window: { resizable: true }, - dragDrop: [], + dragDrop: [ + { + dragSelector: '[data-item-id][draggable="true"]', + dropSelector: null + } + ], contextMenus: [ { handler: CharacterSheet._getContextMenuOptions, @@ -665,11 +670,24 @@ export default class CharacterSheet extends DHBaseActorSheet { } } - async _onDragStart(_, 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/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 21fbfea8..a6413cf0 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', @@ -26,7 +26,8 @@ export default class ArmorSheet extends DHBaseItemSheet { settings: { template: 'systems/daggerheart/templates/sheets/items/armor/settings.hbs', scrollable: ['.settings'] - } + }, + ...super.PARTS, }; /**@inheritdoc */ diff --git a/module/applications/sheets/items/feature.mjs b/module/applications/sheets/items/feature.mjs index a54c3bcb..3d13d7de 100644 --- a/module/applications/sheets/items/feature.mjs +++ b/module/applications/sheets/items/feature.mjs @@ -61,12 +61,17 @@ export default class FeatureSheet extends DHBaseItemSheet { static async selectActionType() { const content = await foundry.applications.handlebars.renderTemplate( 'systems/daggerheart/templates/actionTypes/actionType.hbs', - { types: CONFIG.DH.ACTIONS.actionTypes } + { + types: CONFIG.DH.ACTIONS.actionTypes, + itemName: game.i18n.localize('DAGGERHEART.CONFIG.SelectAction.selectType') + } ), - title = 'Select Action Type'; + title = game.i18n.localize('DAGGERHEART.CONFIG.SelectAction.selectType'); + console.log(this.document); return foundry.applications.api.DialogV2.prompt({ window: { title }, + classes: ['daggerheart', 'dh-style'], content, ok: { label: title, diff --git a/module/applications/sheets/items/weapon.mjs b/module/applications/sheets/items/weapon.mjs index 77396998..e89c3dce 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'], @@ -25,12 +26,13 @@ export default class WeaponSheet extends DHBaseItemSheet { settings: { template: 'systems/daggerheart/templates/sheets/items/weapon/settings.hbs', scrollable: ['.settings'] - } + }, + ...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/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 1d791b46..1974a272 100644 --- a/module/config/itemConfig.mjs +++ b/module/config/itemConfig.mjs @@ -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' } 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 e2eafbf2..8fb15f50 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?.total ?? 1; - actorData.cast = actorData.spellcast?.total ?? 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; 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..cf3669a1 100644 --- a/module/data/actor/adversary.mjs +++ b/module/data/actor/adversary.mjs @@ -5,6 +5,7 @@ import BaseDataActor from './base.mjs'; const resourceField = () => 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: 0, integer: true }) }); @@ -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' } @@ -93,4 +95,9 @@ export default class DhpAdversary extends BaseDataActor { get features() { return this.parent.items.filter(x => x.type === 'feature'); } + + prepareDerivedData() { + this.resources.hitPoints.maxTotal = this.resources.hitPoints.max + this.resources.hitPoints.bonus; + this.resources.stress.maxTotal = this.resources.stress.max + this.resources.stress.bonus; + } } diff --git a/module/data/actor/base.mjs b/module/data/actor/base.mjs index 1b90968d..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 }; } @@ -27,10 +35,16 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel { /** @inheritDoc */ static defineSchema() { const fields = foundry.data.fields; + const schema = {}; - return { - description: new fields.HTMLField({ required: true, nullable: true }) - }; + 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; } /** diff --git a/module/data/actor/character.mjs b/module/data/actor/character.mjs index da60286a..86cb509d 100644 --- a/module/data/actor/character.mjs +++ b/module/data/actor/character.mjs @@ -36,6 +36,7 @@ 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 }), @@ -100,10 +101,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 }) diff --git a/module/data/actor/companion.mjs b/module/data/actor/companion.mjs index 88b149e3..b1c41ea3 100644 --- a/module/data/actor/companion.mjs +++ b/module/data/actor/companion.mjs @@ -20,6 +20,7 @@ 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({ @@ -66,6 +67,7 @@ export default class DhCompanion extends BaseDataActor { damage: { parts: [ { + type: ['physical'], value: { dice: 'd6', multiplier: 'prof' 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 bf2bf73e..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, { 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/weapon.mjs b/module/data/item/weapon.mjs index 80c8271d..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' 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 46e7374e..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); } } 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..0bd45690 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -370,7 +370,10 @@ export default class DhpActor extends Actor { } getRollData() { - return this.system; + const rollData = super.getRollData(); + rollData.prof = this.system.proficiency?.total ?? 1; + rollData.cast = this.system.spellcast?.total ?? 1; + return rollData; } formatRollModifier(roll) { @@ -462,7 +465,7 @@ export default class DhpActor extends Actor { 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 +483,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 +512,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); @@ -553,18 +583,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 - } - }); - } */ } }); } 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/helpers/utils.mjs b/module/helpers/utils.mjs index 8156736d..40a71ac7 100644 --- a/module/helpers/utils.mjs +++ b/module/helpers/utils.mjs @@ -236,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 => { @@ -253,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/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 7466ae8b..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; } 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/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/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 @@
-