diff --git a/README.md b/README.md index 177636c7..6c90e73e 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,17 @@ ## Table of Contents -- [Overview](#overview) -- [User Install Guide](#user-install) -- [Documentation](#documentation) -- [Developer Setup](#development-setup) -- [Contribution Info](#contributing) +- [Foundryborne Daggerheart](#foundryborne-daggerheart) + - [Table of Contents](#table-of-contents) + - [Overview](#overview) + - [User Install](#user-install) + - [Documentation](#documentation) + - [Development Setup](#development-setup) + - [Available Scripts](#available-scripts) + - [Notes](#notes) + - [Contributing](#contributing) + - [AI Policy](#ai-policy) + - [Disclaimer](#disclaimer) ## Overview @@ -72,7 +78,7 @@ Looking to contribute to the project? Look no further, check out our [contributi The Foundryborne Daggerheart system does not make use of AI (generative or otherwise) for any area of its implementation. We expect all contributors to follow this same policy when contributing with a pull request; contributions made using AI will be rejected outright. -## Disclaimer: +## Disclaimer **Daggerheart System** Daggerheart is a trademark of Darrington Press LLC. All original content, mechanics, and intellectual property related to the Daggerheart roleplaying game are © Darrington Press LLC. diff --git a/daggerheart.d.ts b/daggerheart.d.ts index 1641faa8..891a3a2a 100644 --- a/daggerheart.d.ts +++ b/daggerheart.d.ts @@ -4,6 +4,15 @@ import '@common/primitives/global.mjs'; import Canvas from '@client/canvas/board.mjs'; import { ResourceUpdateMap } from './module/data/action/baseAction.mjs'; +import * as applications from './module/applications/_module.mjs'; +import * as data from './module/data/_module.mjs'; +import * as models from './module/data/_module.mjs'; +import * as documents from './module/documents/_module.mjs'; +import { macros } from './module/_module.mjs'; +import * as dice from './module/dice/_module.mjs'; +import * as fields from './module/data/fields/_module.mjs'; + + // Foundry's use of `Object.assign(globalThis) means many globally available objects are not read as such // This declare global hopefully fixes that // Note: eslint is not aware of these, whatever is added here should go in the eslint's globals list @@ -80,3 +89,17 @@ declare global { damageOptions: object; } } + +declare module '@client/packages/system.mjs' { + export default interface System { + api: { + applications: typeof applications, + data: typeof data, + models: typeof models, + documents: typeof documents, + macros: typeof macros, + dice: typeof dice, + fields: typeof fields + }; + } +} diff --git a/lang/en.json b/lang/en.json index af03ab7c..91f41f12 100755 --- a/lang/en.json +++ b/lang/en.json @@ -84,6 +84,7 @@ "transformActorMissing": "The assigned actor to transform into does not exist. It was probably deleted or moved in/out of a compendium", "canvasError": "There is no active scene.", "prototypeError": "You can only use a transform action from a Token", + "linkedSelectedError": "To transform a linked actor there either needs to be only a single token of it on the canvas, or you need to left-click select only one of them.", "actorLinkError": "You cannot transform a token with Actor Link set to true" } }, @@ -113,7 +114,8 @@ "area": { "sectionTitle": "Areas", "shape": "Shape", - "size": "Size" + "size": "Size", + "hasHole": "Area Hole" }, "displayInChat": "Display in chat", "deleteTriggerTitle": "Delete Trigger", @@ -874,6 +876,10 @@ "bruiser": "for each Bruiser adversary.", "solo": "for each Solo adversary." }, + "AreaTypes": { + "attached": { "label": "Attached To Token" }, + "placed": { "label": "Placed In Scene" } + }, "ArmorInteraction": { "none": { "label": "Ignores Armor" }, "active": { "label": "Active w/ Armor" }, @@ -2219,7 +2225,9 @@ }, "Resource": { "single": "Resource", - "plural": "Resources" + "plural": "Resources", + "unloaded": "The weapon is not loaded", + "loaded": "The weapon is loaded" }, "Roll": { "attack": "Attack Roll", @@ -3255,7 +3263,8 @@ "knowTheTide": "Know The Tide gained a token", "lackingItemTransferPermission": "User {user} lacks owner permission needed to transfer items to {target}", "noTokenTargeted": "No token is targeted", - "behaviorRegionRequiresGM": "Creating a Region with an attached Behavior requires an online GM" + "behaviorRegionRequiresGM": "Creating a Region with an attached Behavior requires an online GM", + "reloadRequired": "The {weapon} must be reloaded to be used!" }, "Progress": { "migrationLabel": "Performing system migration. Please wait and do not close Foundry." diff --git a/module/applications/sheets/actors/adversary.mjs b/module/applications/sheets/actors/adversary.mjs index 85380392..b6e17ddd 100644 --- a/module/applications/sheets/actors/adversary.mjs +++ b/module/applications/sheets/actors/adversary.mjs @@ -7,7 +7,7 @@ export default class AdversarySheet extends DHBaseActorSheet { /** @inheritDoc */ static DEFAULT_OPTIONS = { classes: ['adversary'], - position: { width: 660, height: 766 }, + position: { width: 645, height: 750 }, window: { resizable: true }, actions: { toggleHitPoints: AdversarySheet.#toggleHitPoints, @@ -58,12 +58,13 @@ export default class AdversarySheet extends DHBaseActorSheet { template: 'systems/daggerheart/templates/sheets/actors/adversary/features.hbs', scrollable: ['.feature-section'] }, - notes: { - template: 'systems/daggerheart/templates/sheets/actors/adversary/notes.hbs' - }, effects: { template: 'systems/daggerheart/templates/sheets/actors/adversary/effects.hbs', scrollable: ['.effects-sections'] + }, + notes: { + template: 'systems/daggerheart/templates/sheets/actors/adversary/notes.hbs', + scrollable: ['.editor-content'] } }; diff --git a/module/applications/sheets/actors/character.mjs b/module/applications/sheets/actors/character.mjs index 3a60e7ca..ea69f27d 100644 --- a/module/applications/sheets/actors/character.mjs +++ b/module/applications/sheets/actors/character.mjs @@ -3,7 +3,7 @@ import DhDeathMove from '../../dialogs/deathMove.mjs'; import { CharacterLevelup, LevelupViewMode } from '../../levelup/_module.mjs'; import DhCharacterCreation from '../../characterCreation/characterCreation.mjs'; import FilterMenu from '../../ux/filter-menu.mjs'; -import { getArmorSources, getDocFromElement, getDocFromElementSync, sortBy } from '../../../helpers/utils.mjs'; +import { getArmorSources, getDocFromElement, getDocFromElementSync, itemAbleRollParse, sortBy } from '../../../helpers/utils.mjs'; /**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */ @@ -29,6 +29,7 @@ export default class CharacterSheet extends DHBaseActorSheet { toggleResourceDice: CharacterSheet.#toggleResourceDice, handleResourceDice: CharacterSheet.#handleResourceDice, advanceResourceDie: CharacterSheet.#advanceResourceDie, + toggleItemReload: CharacterSheet.#onToggleItemReload, cancelBeastform: CharacterSheet.#cancelBeastform, toggleResourceManagement: CharacterSheet.#toggleResourceManagement, useDowntime: this.useDowntime, @@ -954,11 +955,20 @@ export default class CharacterSheet extends DHBaseActorSheet { }); } - /** */ static #advanceResourceDie(_, target) { this.updateResourceDie(target, true); } + static async #onToggleItemReload(_, target) { + const item = await getDocFromElement(target); + if (!item || !item.system.resource?.max) + return; + + await item.update({ 'system.resource.value': + item.system.needsReload ? itemAbleRollParse(item.system.resource.max, this.document, item) : 0 + }) + } + lowerResourceDie(event) { event.preventDefault(); event.stopPropagation(); @@ -1057,7 +1067,7 @@ export default class CharacterSheet extends DHBaseActorSheet { direction: 'DOWN' }); - html.querySelectorAll('.armor-slot').forEach(element => { + html.querySelectorAll('.armor .slot').forEach(element => { element.addEventListener('click', CharacterSheet.armorSourcePipUpdate); }); } @@ -1072,7 +1082,7 @@ export default class CharacterSheet extends DHBaseActorSheet { /** Update specific armor source */ static async armorSourcePipUpdate(event) { - const target = event.target.closest('.armor-slot'); + const target = event.target.closest('.slot'); const { uuid, value } = target.dataset; const document = await foundry.utils.fromUuid(uuid); @@ -1100,7 +1110,7 @@ export default class CharacterSheet extends DHBaseActorSheet { } const container = target.closest('.slot-bar'); - for (const armorSlot of container.querySelectorAll('.armor-slot i')) { + for (const armorSlot of container.querySelectorAll('.armor .slot i')) { const index = Number.parseInt(armorSlot.dataset.index); if (decreasing && index >= newCurrent) { armorSlot.classList.remove('fa-shield'); diff --git a/module/applications/sheets/api/application-mixin.mjs b/module/applications/sheets/api/application-mixin.mjs index 0168f46d..c99d245d 100644 --- a/module/applications/sheets/api/application-mixin.mjs +++ b/module/applications/sheets/api/application-mixin.mjs @@ -305,7 +305,7 @@ export default function DHApplicationMixin(Base) { _preSyncPartState(partId, newElement, priorElement, state) { super._preSyncPartState(partId, newElement, priorElement, state); for (const el of priorElement.querySelectorAll('.extensible.extended')) { - const { actionId, itemUuid } = el.parentElement.dataset; + const { actionId, itemUuid } = el.closest('[data-item-uuid], [data-action-id]').dataset; const selector = `${actionId ? `[data-action-id="${actionId}"]` : `[data-item-uuid="${itemUuid}"]`} .extensible`; const newExtensible = newElement.querySelector(selector); newExtensible?.classList.add('extended'); diff --git a/module/applications/ui/effectsDisplay.mjs b/module/applications/ui/effectsDisplay.mjs index a64b1b22..c9739f56 100644 --- a/module/applications/ui/effectsDisplay.mjs +++ b/module/applications/ui/effectsDisplay.mjs @@ -39,6 +39,12 @@ export default class DhEffectsDisplay extends HandlebarsApplicationMixin(Applica } }; + /** + * Debounce and slightly delayed request to re-render this panel. Necessary for situations where it is not possible + * to properly wait for promises to resolve before refreshing the UI. + */ + refresh = foundry.utils.debounce(this.render.bind(this), 50); + get element() { return document.body.querySelector('.daggerheart.dh-style.effects-display'); } diff --git a/module/canvas/placeables/regionLayer.mjs b/module/canvas/placeables/regionLayer.mjs index 684fdd5e..b71ccc3f 100644 --- a/module/canvas/placeables/regionLayer.mjs +++ b/module/canvas/placeables/regionLayer.mjs @@ -96,7 +96,7 @@ export default class DhRegionLayer extends foundry.canvas.layers.RegionLayer { return inBounds.length === 1 ? inBounds[0] : null; } - static getTemplateShape({ type, angle, range, direction } = {}) { + static getTemplateShape({ shapeType, angle, range, direction, hasHole } = {}) { const { line, rectangle, inFront, cone, circle, emanation } = CONFIG.DH.GENERAL.templateTypes; /* Length calculation */ @@ -112,11 +112,11 @@ export default class DhRegionLayer extends foundry.canvas.layers.RegionLayer { const shapeData = { ...canvas.mousePosition, - type: type, + type: shapeType, direction: direction ?? 0 }; - switch (type) { + switch (shapeType) { case rectangle.id: shapeData.width = length; shapeData.height = length; @@ -145,7 +145,8 @@ export default class DhRegionLayer extends foundry.canvas.layers.RegionLayer { y: 0, width: 1, height: 1, - shape: game.canvas.grid.isHexagonal ? CONST.TOKEN_SHAPES.ELLIPSE_1 : CONST.TOKEN_SHAPES.RECTANGLE_1 + shape: game.canvas.grid.isHexagonal ? CONST.TOKEN_SHAPES.ELLIPSE_1 : CONST.TOKEN_SHAPES.RECTANGLE_1, + hole: hasHole }; break; } diff --git a/module/config/actionConfig.mjs b/module/config/actionConfig.mjs index a6c04e18..e413a828 100644 --- a/module/config/actionConfig.mjs +++ b/module/config/actionConfig.mjs @@ -119,6 +119,10 @@ export const advantageState = { export const areaTypes = { placed: { id: 'placed', - label: 'Placed Area' + label: 'DAGGERHEART.CONFIG.AreaTypes.placed.label' + }, + attached: { + id: 'attached', + label: 'DAGGERHEART.CONFIG.AreaTypes.attached.label' } }; diff --git a/module/data/action/attackAction.mjs b/module/data/action/attackAction.mjs index 6ebc262f..7024c96e 100644 --- a/module/data/action/attackAction.mjs +++ b/module/data/action/attackAction.mjs @@ -51,6 +51,10 @@ export default class DHAttackAction extends DHDamageAction { } async use(event, options) { + if (this.item?.system.needsReload) { + return ui.notifications.error(game.i18n.format('DAGGERHEART.UI.Notifications.reloadRequired', { weapon: this.item.name })); + } + const result = await super.use(event, options); if (result?.message?.system.action?.roll?.type === 'attack') { @@ -70,12 +74,12 @@ export default class DHAttackAction extends DHDamageAction { game.dice3d.showForRoll(roll, game.user, true); } - const needsToReload = roll.total === 1; + const needsToReload = true; + // const needsToReload = roll.total === 1; if (needsToReload) { - // TODO: Update item resource value when the functionality has been added to the system - // this.item.update({ 'system.resource.value': 0 }); + this.item.update({ 'system.resource.value': 0 }); } - return true; + return needsToReload; } diff --git a/module/data/action/baseAction.mjs b/module/data/action/baseAction.mjs index 58be672b..be7224cd 100644 --- a/module/data/action/baseAction.mjs +++ b/module/data/action/baseAction.mjs @@ -114,7 +114,10 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel return this._id; } - /** Returns true if the current user is the owner of the containing item */ + /** + * Returns true if the current user is the owner of the containing item. + * @returns {boolean} + */ get isOwner() { return this.item?.isOwner ?? true; } @@ -143,6 +146,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel /** * Return the first Actor parent found. + * @returns {DhpActor | null} */ get actor() { return this.item instanceof DhpActor @@ -155,6 +159,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel /** * Returns true if the action is usable. * An action is usable on any actor type. For example, an adversary might have a base attack action. + * @returns {boolean} */ get usable() { const actor = this.actor; diff --git a/module/data/actor/tierAdjustment.mjs b/module/data/actor/tierAdjustment.mjs index bc6ad176..8b9e5bdc 100644 --- a/module/data/actor/tierAdjustment.mjs +++ b/module/data/actor/tierAdjustment.mjs @@ -2,8 +2,14 @@ import { calculateExpectedValue, parseTermsFromSimpleFormula } from '../../helpe import { adversaryExpectedDamage, adversaryScalingData } from '../../config/actorConfig.mjs'; import { parseInlineParams } from '../../enrichers/parser.mjs'; +/** + * Accepts source data for an adversary and a target tier, and returns new source data + * @type {object} source + * @type {number} tier + * @returns {object} adjusted source data + */ export function getTierAdjustedAdversary(source, tier) { - const currentTier = source.tier ?? 1; + const currentTier = source.system.tier ?? 1; /** @type {(2 | 3 | 4)[]} */ const tiers = new Array(Math.abs(tier - currentTier)) @@ -35,7 +41,7 @@ export function getTierAdjustedAdversary(source, tier) { // Store initial attack damage for abilities that have you deal a "standard attack" const initialAttack = { type: source.system.attack.damage?.parts.hitPoints?.type?.toSorted(), - value: getDamagePartsFormula(source.system.attack.damage?.parts.hitPoints?.value) + value: getFormula(source.system.attack.damage?.parts.hitPoints?.value) }; // Update damage of base attack. @@ -45,9 +51,9 @@ export function getTierAdjustedAdversary(source, tier) { for (const property of ['value', 'valueAlt']) { const data = damage.parts.hitPoints[property]; - const previousFormula = getDamagePartsFormula(data); - const { value, formula } = calculateAdjustedDamage(previousFormula, 'attack', damageMeta); - applyAdjustedDamage(data, value, formula); + const previousFormula = getFormula(data); + const value = calculateAdjustedDamage(previousFormula, 'attack', damageMeta); + applyAdjustedDamage(data, value); } } catch (err) { ui.notifications.warn('Failed to convert attack damage of adversary'); @@ -65,7 +71,7 @@ export function getTierAdjustedAdversary(source, tier) { if (!formula) return match; try { - const newFormula = calculateAdjustedDamage(formula, 'action', damageMeta)?.formula; + const newFormula = getFormula(calculateAdjustedDamage(formula, 'action', damageMeta)); descriptionFormulas.push(formula); return match.replace(formula, newFormula); } catch { @@ -82,15 +88,15 @@ export function getTierAdjustedAdversary(source, tier) { const result = []; for (const property of ['value', 'valueAlt']) { const { [property]: data, type: damageType } = action.damage.parts.hitPoints; - const previousFormula = getDamagePartsFormula(data); + const previousFormula = getFormula(data); const isActuallyAttack = previousFormula === initialAttack.value && foundry.utils.equals(damageType.toSorted(), initialAttack.type) && !descriptionFormulas.includes(previousFormula); const type = isActuallyAttack ? 'attack' : 'action'; - const { value, formula } = calculateAdjustedDamage(previousFormula, type, damageMeta); - applyAdjustedDamage(data, value, formula); - result.push({ previousFormula, formula }); + const value = calculateAdjustedDamage(previousFormula, type, damageMeta); + applyAdjustedDamage(data, value); + result.push({ previousFormula, formula: getFormula(value) }); } // Override text in the description with those values @@ -189,24 +195,30 @@ function calculateAdjustedDamage(formula, type, { currentDamageRange, newDamageR value.bonus = Math.round(expected - getBaseAverage()); } - const newFormula = [value.diceQuantity ? `${value.diceQuantity}d${value.faces}` : null, value.bonus] - .filter(p => !!p) - .join('+'); - return { value, formula: newFormula }; + return value; } -function getDamagePartsFormula(data) { - return data.custom.enabled - ? data.custom.formula - : [data.flatMultiplier ? `${data.flatMultiplier}${data.dice}` : 0, data.bonus ?? 0].filter(p => !!p).join('+'); +/** + * Get formula from either damage parts *or* a simple formula object. + * @returns {string} the new formula data + */ +function getFormula(data) { + if (data.custom?.enabled) { + return data.custom.formula; + } + + const diceQuantity = data.flatMultiplier ?? data.diceQuantity; + const dice = data.faces ? `d${data.faces}` : data.dice; + const mod = data.bonus; + return [diceQuantity ? `${diceQuantity}${dice}` : 0, mod].filter(p => !!p).join('+'); } /** * Updates damage to reflect a specific value. - * @throws if damage structure is invalid for conversion - * @returns the converted formula and value as a simplified term, or null if it doesn't deal HP damage + * @param {object} diceData + * @param {object} value */ -function applyAdjustedDamage(diceData, value, formula) { +function applyAdjustedDamage(diceData, value) { if (value.diceQuantity) { diceData.custom.enabled = false; diceData.bonus = value.bonus; @@ -214,6 +226,6 @@ function applyAdjustedDamage(diceData, value, formula) { diceData.flatMultiplier = value.diceQuantity; } else if (!value.diceQuantity) { diceData.custom.enabled = true; - diceData.custom.formula = formula; + diceData.custom.formula = getFormula(value); } } diff --git a/module/data/fields/action/areasField.mjs b/module/data/fields/action/areasField.mjs index 52110045..a4180101 100644 --- a/module/data/fields/action/areasField.mjs +++ b/module/data/fields/action/areasField.mjs @@ -33,6 +33,10 @@ export default class AreasField extends fields.ArrayField { initial: CONFIG.DH.GENERAL.range.veryClose.id, label: 'DAGGERHEART.ACTIONS.Config.area.size' }), + hasHole: new fields.BooleanField({ + initial: false, + label: 'DAGGERHEART.ACTIONS.Config.area.hasHole' + }), effects: new fields.ArrayField(new fields.DocumentIdField()) }); super(element, options, context); diff --git a/module/data/fields/action/transformField.mjs b/module/data/fields/action/transformField.mjs index 07b32166..0fc35742 100644 --- a/module/data/fields/action/transformField.mjs +++ b/module/data/fields/action/transformField.mjs @@ -1,5 +1,11 @@ +import { getWorldActor } from '../../../helpers/utils.mjs'; + const fields = foundry.data.fields; +/** + * @import DHSummonAction from '../../action/summonAction.mjs' + */ + export default class DHSummonField extends fields.SchemaField { /** * Action Workflow order @@ -20,6 +26,11 @@ export default class DHSummonField extends fields.SchemaField { super(transformFields, options, context); } + /** + * Runs the execute. This is run on behalf of DHSummonAction. + * @todo move this function to be on the summon action. + * @this DHSummonAction + */ static async execute() { if (!this.transform.actorUUID) { ui.notifications.warn(game.i18n.localize('DAGGERHEART.ACTIONS.TYPES.transform.noTransformActor')); @@ -37,26 +48,37 @@ export default class DHSummonField extends fields.SchemaField { return false; } - if (this.actor.prototypeToken.actorLink) { - ui.notifications.warn(game.i18n.localize('DAGGERHEART.ACTIONS.TYPES.transform.actorLinkError')); + const activeTokens = this.actor.getActiveTokens(false, true); + const controlledMatchingTokens = canvas.tokens.controlled + .filter(x => x.actor && x.actor.uuid === this.actor.uuid) + .map(x => x.document); + /** @type {typeof game.system.api.documents.DhToken | null} */ + const token = this.actor.token ?? ( + activeTokens.length === 1 ? activeTokens[0] : + (controlledMatchingTokens.length === 1 ? controlledMatchingTokens[0] : null) + ); + + if (!this.actor.token && !token) { + ui.notifications.warn(game.i18n.localize('DAGGERHEART.ACTIONS.TYPES.transform.linkedSelectedError')); return false; } - if (!this.actor.token) { + if (!token) { ui.notifications.warn(game.i18n.localize('DAGGERHEART.ACTIONS.TYPES.transform.prototypeError')); return false; } - const actor = await DHSummonField.getWorldActor(baseActor); + const actor = await getWorldActor(baseActor); const tokenSizes = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).tokenSizes; const tokenSize = actor?.system.metadata.usesSize ? tokenSizes[actor.system.size] : actor.prototypeToken.width; - await this.actor.token.update( - { ...actor.prototypeToken.toJSON(), actorId: actor.id, width: tokenSize, height: tokenSize }, - { diff: false, recursive: false, noHook: true } + // Update token. Avoid using recursive: false, since that prevents animations + await token.update( + { ...actor.prototypeToken.toObject(), actorId: actor.id, width: tokenSize, height: tokenSize }, + { diff: false, noHook: true } ); - if (this.actor.token.combatant) { + if (token.combatant) { this.actor.token.combatant.update({ actorId: actor.id, img: actor.prototypeToken.texture.src }); } @@ -64,17 +86,17 @@ export default class DHSummonField extends fields.SchemaField { if (!this.transform.resourceRefresh.hitPoints) { marks.hitPoints = Math.min( this.actor.system.resources.hitPoints.value, - this.actor.token.actor.system.resources.hitPoints.max - 1 + token.actor.system.resources.hitPoints.max - 1 ); } if (!this.transform.resourceRefresh.stress) { marks.stress = Math.min( this.actor.system.resources.stress.value, - this.actor.token.actor.system.resources.stress.max - 1 + token.actor.system.resources.stress.max - 1 ); } if (marks.hitPoints || marks.stress) { - this.actor.token.actor.update({ + token.actor.update({ 'system.resources': { hitPoints: { value: marks.hitPoints }, stress: { value: marks.stress } @@ -84,20 +106,9 @@ export default class DHSummonField extends fields.SchemaField { const prevPosition = { ...this.actor.sheet.position }; this.actor.sheet.close(); - this.actor.token.actor.sheet.render({ force: true, position: prevPosition }); - } - - /* Check for any available instances of the actor present in the world, or create a world actor based on compendium */ - static async getWorldActor(baseActor) { - if (!baseActor.inCompendium) return baseActor; - - const dataType = game.system.api.data.actors[`Dh${baseActor.type.capitalize()}`]; - if (dataType && baseActor.img === dataType.DEFAULT_ICON) { - const worldActorCopy = game.actors.find(x => x.name === baseActor.name); - if (worldActorCopy) return worldActorCopy; + token.actor.sheet.render({ force: true, position: prevPosition }); + if (token.object.controlled) { + ui.effectsDisplay.refresh(); } - - const worldActor = await game.system.api.documents.DhpActor.create(baseActor.toObject()); - return worldActor; } } diff --git a/module/data/item/weapon.mjs b/module/data/item/weapon.mjs index b004166c..96550bc3 100644 --- a/module/data/item/weapon.mjs +++ b/module/data/item/weapon.mjs @@ -9,7 +9,8 @@ export default class DHWeapon extends AttachableItem { type: 'weapon', hasDescription: true, isInventoryItem: true, - hasActions: true + hasActions: true, + hasResource: true }); } @@ -121,6 +122,10 @@ export default class DHWeapon extends AttachableItem { return Boolean(this.weaponFeatures.find(x => x.value === 'reloading')); } + get needsReload() { + return this.hasReload && this.resource.value === 0; + } + /**@inheritdoc */ async getDescriptionData() { const baseDescription = this.description; diff --git a/module/documents/_types.d.ts b/module/documents/_types.d.ts index a94d6395..a3af1b33 100644 --- a/module/documents/_types.d.ts +++ b/module/documents/_types.d.ts @@ -3,17 +3,26 @@ import DHItem from './item.mjs'; import BaseDataItem from '../data/item/base.mjs'; import DhActiveEffect from './activeEffect.mjs'; import EmbeddedCollection from '@common/abstract/embedded-collection.mjs'; +import DHToken from './token.mjs'; +import Actor from '@client/documents/actor.mjs'; +import Item from '@client/documents/item.mjs'; declare module './actor.mjs' { - export default interface DhpActor { + export default interface DhpActor extends Actor { system: T; items: EmbeddedCollection; effects: EmbeddedCollection; + get token(): DHToken | null; + + /** @inheritdoc */ + getActiveTokens(linked?: boolean, document?: boolean): (DHToken | foundry.canvas.placeables.Token)[]; + getActiveTokens(linked?: boolean, document: true): DHToken[]; + getActiveTokens(linked?: boolean, document: false): foundry.canvas.placeables.Token[]; } } declare module './item.mjs' { - export default interface DHItem { + export default interface DHItem extends Item { parent: DhpActor; actor: DhpActor; system: T; diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index 8ef64f65..30901374 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -34,12 +34,14 @@ export default class DhpActor extends Actor { super.prepareData(); // Update effects if it is the user's character or is controlled - if (canvas.ready) { + // A timeout avoids an infinite loop when accessing token actors before the delta is finished constructing + window.setTimeout(() => { + if (!canvas.ready) return; const controlled = canvas.tokens.controlled.some(t => t.actor === this); if (game.user.character === this || controlled) { - ui.effectsDisplay.render(); + ui.effectsDisplay.refresh(); } - } + }, 0); } /* -------------------------------------------- */ diff --git a/module/documents/chatMessage.mjs b/module/documents/chatMessage.mjs index b555dfca..fd68997c 100644 --- a/module/documents/chatMessage.mjs +++ b/module/documents/chatMessage.mjs @@ -276,8 +276,12 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage { async onCreateAreas(event) { const createArea = async selectedArea => { const effects = selectedArea.effects.map(effect => this.system.action.item.effects.get(effect).uuid); - const { shape: type, size: range } = selectedArea; - const shapeData = CONFIG.Canvas.layers.regions.layerClass.getTemplateShape({ type, range }); + const { shape: shapeType, size: range, hasHole } = selectedArea; + const shapeData = CONFIG.Canvas.layers.regions.layerClass.getTemplateShape({ + shapeType, + range, + hasHole + }); const scene = game.scenes.get(game.user.viewedScene); const level = scene.levels.find(x => x.isView); @@ -305,7 +309,10 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage { visibility: CONST.REGION_VISIBILITY.ALWAYS }; const placeRegion = data => { - canvas.regions.placeRegion(data, { create: true }); + canvas.regions.placeRegion(data, { + create: true, + attachToToken: selectedArea.type === CONFIG.DH.ACTIONS.areaTypes.attached.id + }); }; // Regions with effects must be placed by the GM diff --git a/module/documents/token.mjs b/module/documents/token.mjs index 8e91d4f0..d70480db 100644 --- a/module/documents/token.mjs +++ b/module/documents/token.mjs @@ -38,7 +38,8 @@ export default class DHToken extends CONFIG.Token.documentClass { tokens.filter(x => x.actor).map(x => x.actor) ); } - super.createCombatants(tokens, combat ?? {}); + + await super.createCombatants(tokens, combat ?? {}); } /**@inheritdoc */ diff --git a/module/enrichers/TemplateEnricher.mjs b/module/enrichers/TemplateEnricher.mjs index bbe93b17..4478ea14 100644 --- a/module/enrichers/TemplateEnricher.mjs +++ b/module/enrichers/TemplateEnricher.mjs @@ -58,10 +58,11 @@ export const renderMeasuredTemplate = async event => { if (!type || !range || !game.canvas.scene) return; const shapeData = CONFIG.Canvas.layers.regions.layerClass.getTemplateShape({ - type, + shapetype: type, angle, range, - direction + direction, + hasHole: false }); await canvas.regions.placeRegion( diff --git a/module/helpers/utils.mjs b/module/helpers/utils.mjs index cb79a76b..84bcacf2 100644 --- a/module/helpers/utils.mjs +++ b/module/helpers/utils.mjs @@ -1,6 +1,10 @@ import { diceTypes, getDiceSoNicePresets, getDiceSoNicePreset, range } from '../config/generalConfig.mjs'; import Tagify from '@yaireo/tagify'; +/** + * @import DhpActor from '../documents/actor.mjs'; + */ + export const capitalize = string => { return string.charAt(0).toUpperCase() + string.slice(1); }; @@ -891,15 +895,21 @@ export function shouldUseHopeFearAutomation(options = { gmAsPlayer: true }) { return (!game.user.isGM || options.gmAsPlayer) ? hopeFear.players : hopeFear.gm; } +/** + * Returns the given actor if its a world actor, + * finds a world actor equivalent, + * or imports the actor and returns the imported actor. + * @param {DhpActor} baseActor + * @returns {Promise} a world actor + */ export async function getWorldActor(baseActor) { if (baseActor.inCompendium) { - const worldActorCopy = game.actors.find(x => - x._stats.compendiumSource === baseActor.uuid && - (!x.prototypeToken.actorLink || x.name === baseActor.name) + const worldActorCandidates = game.actors.filter(x => + x._stats.compendiumSource === baseActor.uuid && + x.prototypeToken.actorLink === baseActor.prototypeToken.actorLink ); - - if (worldActorCopy) - return worldActorCopy; + const worldActorCopy = worldActorCandidates.find(a => a.name === baseActor.name) ?? worldActorCandidates[0]; + if (worldActorCopy) return worldActorCopy; const baseActorData = baseActor; return await game.system.api.documents.DhpActor.create({ diff --git a/src/packs/items/weapons/weapon_Black_Powder_Revolver_AokqTusPzn0hghkE.json b/src/packs/items/weapons/weapon_Black_Powder_Revolver_AokqTusPzn0hghkE.json index 34371c2b..f8b254f3 100644 --- a/src/packs/items/weapons/weapon_Black_Powder_Revolver_AokqTusPzn0hghkE.json +++ b/src/packs/items/weapons/weapon_Black_Powder_Revolver_AokqTusPzn0hghkE.json @@ -128,6 +128,15 @@ "source": "Daggerheart SRD", "page": 48, "artist": "" + }, + "resource": { + "type": "simple", + "value": 1, + "max": "1", + "recovery": null, + "progression": "decreasing", + "dieFaces": "d4", + "icon": "fa-solid fa-gun" } }, "effects": [], diff --git a/src/packs/items/weapons/weapon_Blunderbuss_SLFrK0WmldPo0shz.json b/src/packs/items/weapons/weapon_Blunderbuss_SLFrK0WmldPo0shz.json index 9b2f455a..570d8856 100644 --- a/src/packs/items/weapons/weapon_Blunderbuss_SLFrK0WmldPo0shz.json +++ b/src/packs/items/weapons/weapon_Blunderbuss_SLFrK0WmldPo0shz.json @@ -128,6 +128,15 @@ "source": "Daggerheart SRD", "page": 46, "artist": "" + }, + "resource": { + "type": "simple", + "value": 1, + "max": "1", + "recovery": null, + "progression": "decreasing", + "dieFaces": "d4", + "icon": "fa-solid fa-gun" } }, "effects": [], diff --git a/src/packs/items/weapons/weapon_Hand_Cannon_MyGz8nd5sieRQ7zl.json b/src/packs/items/weapons/weapon_Hand_Cannon_MyGz8nd5sieRQ7zl.json index 4967c6e4..f9d54bcd 100644 --- a/src/packs/items/weapons/weapon_Hand_Cannon_MyGz8nd5sieRQ7zl.json +++ b/src/packs/items/weapons/weapon_Hand_Cannon_MyGz8nd5sieRQ7zl.json @@ -128,6 +128,15 @@ "source": "Daggerheart SRD", "page": 50, "artist": "" + }, + "resource": { + "type": "simple", + "value": 1, + "max": "1", + "recovery": null, + "progression": "decreasing", + "dieFaces": "d4", + "icon": "fa-solid fa-gun" } }, "effects": [], diff --git a/src/packs/items/weapons/weapon_Ilmari_s_Rifle_TMrUzVC3KvcHmdt8.json b/src/packs/items/weapons/weapon_Ilmari_s_Rifle_TMrUzVC3KvcHmdt8.json index cddd762a..512168f5 100644 --- a/src/packs/items/weapons/weapon_Ilmari_s_Rifle_TMrUzVC3KvcHmdt8.json +++ b/src/packs/items/weapons/weapon_Ilmari_s_Rifle_TMrUzVC3KvcHmdt8.json @@ -128,6 +128,15 @@ "source": "Daggerheart SRD", "page": 49, "artist": "" + }, + "resource": { + "type": "simple", + "value": 1, + "max": "1", + "recovery": null, + "progression": "decreasing", + "dieFaces": "d4", + "icon": "fa-solid fa-gun" } }, "effects": [], diff --git a/src/packs/items/weapons/weapon_Magus_Revolver_jGykNGQiKm63tCiE.json b/src/packs/items/weapons/weapon_Magus_Revolver_jGykNGQiKm63tCiE.json index 9dbbb1c1..fd3e86d3 100644 --- a/src/packs/items/weapons/weapon_Magus_Revolver_jGykNGQiKm63tCiE.json +++ b/src/packs/items/weapons/weapon_Magus_Revolver_jGykNGQiKm63tCiE.json @@ -128,6 +128,15 @@ "source": "Daggerheart SRD", "page": 51, "artist": "" + }, + "resource": { + "type": "simple", + "value": 1, + "max": "1", + "recovery": null, + "progression": "decreasing", + "dieFaces": "d4", + "icon": "fa-solid fa-gun" } }, "effects": [], diff --git a/src/packs/subclasses/feature_Elemental_Aura_2JH9NaOh69yN80Gw.json b/src/packs/subclasses/feature_Elemental_Aura_2JH9NaOh69yN80Gw.json index 7a3ebd91..e0476a56 100644 --- a/src/packs/subclasses/feature_Elemental_Aura_2JH9NaOh69yN80Gw.json +++ b/src/packs/subclasses/feature_Elemental_Aura_2JH9NaOh69yN80Gw.json @@ -52,7 +52,7 @@ "includeBase": false }, "target": { - "type": "hostile", + "type": "", "amount": 1 }, "effects": [ @@ -63,13 +63,23 @@ ], "name": "Fire", "img": "icons/magic/fire/barrier-wall-explosion-orange.webp", - "range": "close" + "range": "", + "areas": [ + { + "name": "Elemental Aura: Fire", + "type": "attached", + "shape": "emanation", + "size": "close", + "hasHole": false, + "effects": [] + } + ] }, "xDBLH5TWidvrYF7z": { "type": "effect", "_id": "xDBLH5TWidvrYF7z", "systemPath": "actions", - "description": "

When an adversary marks 1 or more Hit Points, they must also mark a Stress.

", + "description": "

Your allies gain a +1 bonus to Strength.

", "chatDisplay": true, "actionType": "action", "cost": [], @@ -78,19 +88,26 @@ "max": "", "recovery": null }, - "effects": [ - { - "_id": "WRuijfHxmUscAa69", - "onSave": false - } - ], + "effects": [], "target": { - "type": "friendly", + "type": "", "amount": null }, "name": "Earth", "img": "icons/magic/control/buff-strength-muscle-damage-red.webp", - "range": "close" + "range": "", + "areas": [ + { + "name": "Elemental Aura: Earth", + "type": "attached", + "shape": "emanation", + "size": "close", + "hasHole": true, + "effects": [ + "yvpWNCNobg0LLjey" + ] + } + ] }, "S4t5HlgxWlHwaBDw": { "type": "effect", @@ -112,12 +129,22 @@ } ], "target": { - "type": "hostile", + "type": "self", "amount": null }, "name": "Water", "img": "icons/magic/water/vortex-water-whirlpool.webp", - "range": "close" + "range": "self", + "areas": [ + { + "name": "Elemental Aura: Water", + "type": "attached", + "shape": "emanation", + "size": "close", + "hasHole": false, + "effects": [] + } + ] }, "hAsKFFewtTqd1gg9": { "type": "attack", @@ -138,15 +165,10 @@ "includeBase": false }, "target": { - "type": "any", + "type": "self", "amount": null }, - "effects": [ - { - "_id": "mJBA2QTyM9SM5NVS", - "onSave": false - } - ], + "effects": [], "roll": { "type": "diceSet", "trait": null, @@ -169,7 +191,19 @@ }, "name": "Air", "img": "icons/magic/air/air-burst-spiral-blue-gray.webp", - "range": "" + "range": "self", + "areas": [ + { + "name": "Elemental Aura: Air", + "type": "attached", + "shape": "emanation", + "size": "close", + "hasHole": false, + "effects": [ + "vnKV5hsO4DVjURAl" + ] + } + ] } }, "originItemType": null, @@ -181,49 +215,6 @@ } }, "effects": [ - { - "name": "Elemental Aura (Earth)", - "img": "icons/magic/control/buff-strength-muscle-damage-orange.webp", - "origin": "Compendium.daggerheart.subclasses.Item.2JH9NaOh69yN80Gw", - "transfer": false, - "_id": "WRuijfHxmUscAa69", - "type": "base", - "system": { - "rangeDependence": { - "enabled": false, - "type": "withinRange", - "target": "hostile", - "range": "melee" - } - }, - "changes": [ - { - "key": "system.traits.strength.value", - "mode": 2, - "value": "1", - "priority": null - } - ], - "disabled": false, - "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null - }, - "description": "", - "tint": "#ffffff", - "statuses": [], - "sort": 0, - "flags": {}, - "_stats": { - "compendiumSource": null - }, - "_key": "!items.effects!2JH9NaOh69yN80Gw.WRuijfHxmUscAa69" - }, { "name": "Elemental Aura (Water)", "img": "icons/magic/water/vortex-water-whirlpool.webp", @@ -237,18 +228,15 @@ "type": "withinRange", "target": "hostile", "range": "melee" - } + }, + "changes": [] }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "", "tint": "#ffffff", @@ -258,6 +246,9 @@ "_stats": { "compendiumSource": null }, + "start": null, + "showIcon": 1, + "folder": null, "_key": "!items.effects!2JH9NaOh69yN80Gw.H7W52ps5d3UGmaFr" }, { @@ -273,18 +264,15 @@ "type": "withinRange", "target": "hostile", "range": "melee" - } + }, + "changes": [] }, - "changes": [], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "", "tint": "#ffffff", @@ -294,43 +282,120 @@ "_stats": { "compendiumSource": null }, + "start": null, + "showIcon": 1, + "folder": null, "_key": "!items.effects!2JH9NaOh69yN80Gw.WX5AMEpmUAutB9Hm" }, { - "name": "Elemental Aura (Air)", - "img": "icons/magic/air/air-burst-spiral-blue-gray.webp", - "origin": "Compendium.daggerheart.subclasses.Item.2JH9NaOh69yN80Gw", + "name": "Elemental Aura: Earth", + "disabled": false, + "img": "icons/magic/control/buff-strength-muscle-damage-red.webp", + "description": "

Your allies gain a +1 bonus to Strength.

", "transfer": false, - "_id": "mJBA2QTyM9SM5NVS", - "type": "base", + "statuses": [], "system": { "rangeDependence": { "enabled": false, "type": "withinRange", "target": "hostile", "range": "melee" - } + }, + "changes": [ + { + "key": "system.traits.strength.value", + "type": "add", + "value": 1, + "priority": null, + "phase": "initial" + } + ], + "duration": { + "description": "", + "type": "" + }, + "stacking": null, + "targetDispositions": [ + 1 + ] }, - "changes": [], - "disabled": false, - "duration": { - "startTime": null, + "_id": "yvpWNCNobg0LLjey", + "type": "base", + "start": { + "time": 0, "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "combatant": null, + "initiative": null, + "round": null, + "turn": null }, - "description": "", + "duration": { + "value": null, + "units": "seconds", + "expiry": null, + "expired": false + }, + "origin": null, "tint": "#ffffff", - "statuses": [], + "showIcon": 1, + "folder": null, "sort": 0, "flags": {}, "_stats": { "compendiumSource": null }, - "_key": "!items.effects!2JH9NaOh69yN80Gw.mJBA2QTyM9SM5NVS" + "_key": "!items.effects!2JH9NaOh69yN80Gw.yvpWNCNobg0LLjey" + }, + { + "name": "Elemental Aura: Air", + "disabled": false, + "img": "icons/magic/air/air-burst-spiral-blue-gray.webp", + "description": "

When you or an ally takes damage from an attack beyond Melee range, reduce the damage by 1d8.

", + "transfer": false, + "statuses": [], + "system": { + "rangeDependence": { + "enabled": false, + "type": "withinRange", + "target": "hostile", + "range": "melee" + }, + "changes": [], + "duration": { + "description": "", + "type": "" + }, + "stacking": null, + "targetDispositions": [ + 1 + ] + }, + "_id": "vnKV5hsO4DVjURAl", + "type": "base", + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "duration": { + "value": null, + "units": "seconds", + "expiry": null, + "expired": false + }, + "origin": null, + "tint": "#ffffff", + "showIcon": 1, + "folder": null, + "sort": 0, + "flags": {}, + "_stats": { + "compendiumSource": null + }, + "_key": "!items.effects!2JH9NaOh69yN80Gw.vnKV5hsO4DVjURAl" } ], "sort": 100000, diff --git a/styles/less/global/elements.less b/styles/less/global/elements.less index e57ba50d..d570c08c 100755 --- a/styles/less/global/elements.less +++ b/styles/less/global/elements.less @@ -261,13 +261,14 @@ fieldset { align-items: center; - margin-top: 5px; + margin: 5px 0 0 0; border-radius: 6px; border-color: @color-fieldset-border; + padding-inline: 0.625rem; &.glassy { background-color: light-dark(@dark-blue-10, @golden-10); - border-color: transparent; + border: none; legend { padding: 2px 12px; @@ -384,6 +385,10 @@ justify-content: space-between; } + label { + white-space: nowrap; + } + .btn { padding-top: 15px; } @@ -399,6 +404,13 @@ > .checkbox { align-self: end; } + + .auto-sized { + width: auto; + display: flex; + flex-direction: column; + align-items: center; + } } .form-group { @@ -535,50 +547,39 @@ transform-origin: top; } - .item-buttons { - grid-column: span 3; + /* A multi-button element used to attach resources to other buttons */ + .item-button { display: flex; - gap: 8px; - flex-wrap: wrap; - margin-top: 2px; + button { + color: light-dark(@dark-blue, @dark-blue); + white-space: nowrap; + border: none; - .item-button { - display: flex; - border: 1px solid light-dark(#18162e, #18162e); - color: light-dark(#18162e, #18162e); - outline: none; - box-shadow: none; - border-radius: 6px; + &:hover { + color: @color-text-emphatic; + } - button { - border-radius: 3px 0px 0px 3px; - color: light-dark(@dark-blue, @dark-blue); - white-space: nowrap; - border: 0; + &:not(:first-child) { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + padding: 6px; + background: light-dark(@dark-blue-10, @golden-secondary); + color: light-dark(@dark-blue, @dark-golden); &:hover { - color: @color-text-emphatic; - } - - &:not(:first-child) { - padding: 6px; - background: light-dark(@dark-blue-10, @golden-secondary); - border-radius: 0px 3px 3px 0px; - color: light-dark(@dark-blue, @dark-golden); - - &:hover { - background: light-dark(@light-black, @dark-blue); - color: light-dark(@dark-blue, @golden-secondary); - } + background: light-dark(@light-black, @dark-blue); + color: light-dark(@dark-blue, @golden-secondary); } } - .spacer { + &:not(:last-child) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; border-right: 1px solid black; - content: ''; } } } + .artist-attribution { width: 100%; display: flex; diff --git a/styles/less/global/inventory-item.less b/styles/less/global/inventory-item.less index fc73ba95..b80be41b 100644 --- a/styles/less/global/inventory-item.less +++ b/styles/less/global/inventory-item.less @@ -144,6 +144,10 @@ display: flex; align-items: center; gap: 4px; + + .unloaded { + opacity: 0.5; + } } } @@ -271,6 +275,14 @@ grid-area: labels; } } + + .item-buttons { + grid-column: span 3; + display: flex; + gap: 4px; + flex-wrap: wrap; + margin-top: 2px; + } } .card-item { diff --git a/styles/less/global/resource-bar.less b/styles/less/global/resource-bar.less index d06b43a8..3560285a 100644 --- a/styles/less/global/resource-bar.less +++ b/styles/less/global/resource-bar.less @@ -9,111 +9,108 @@ } }); +/** Pips styling, can exist standalone even without a slot-value */ +.slot-bar { + display: flex; + gap: 4px; + padding: 5px; + border: 1px solid @color-border; + border-radius: 6px; + z-index: 1; + color: @color-text-emphatic; + width: fit-content; + min-height: 22px; + flex-wrap: wrap; + + .slot { + transition: all 0.3s ease; + cursor: pointer; + } + + .slot:not(:has(i)) { + width: 15px; + height: 10px; + border: 1px solid @color-border; + background: light-dark(@dark-blue-10, @golden-10); + border-radius: 3px; + + &.large { + width: 20px; + } + + &.filled { + background: light-dark(@dark-blue, @golden); + } + } + + &.armor .slot { + font-size: var(--font-size-12); + .fa-shield-halved { + color: light-dark(@dark-blue-40, @golden-40); + } + } + + .empty-slot { + width: 15px; + height: 10px; + } +} + +.slot-value { + display: flex; + flex-direction: column; + font-size: 1.5rem; + align-items: center; + justify-content: center; + text-align: center; + z-index: 2; + color: @beige; + + .slot-label { + display: flex; + align-items: center; + color: light-dark(@beige, @dark-blue); + background: light-dark(@dark-blue, @golden); + padding: 0 5px; + width: fit-content; + font-weight: bold; + border-radius: 0px 0px 5px 5px; + font-size: var(--font-size-12); + + .label { + padding-right: 5px; + } + + .value { + padding-left: 6px; + border-left: 1px solid light-dark(@beige, @dark-golden); + } + } +} + .status-bar { display: flex; - justify-content: center; + flex-direction: column; + align-items: center; position: relative; - width: 120px; - height: 40px; - - .status-label { - position: relative; - top: 40px; - height: 22px; - width: 79px; - clip-path: path('M0 0H79L74 16.5L39 22L4 16.5L0 0Z'); - background: light-dark(@dark-blue, @golden); - - h4 { - font-weight: bold; - text-align: center; - line-height: 18px; - color: light-dark(@beige, @dark-blue); - } - } - .slot-value { - position: absolute; - display: flex; - flex-direction: column; - padding: 0 5px; - font-size: 1.5rem; - align-items: center; - width: 140px; - height: 40px; - justify-content: center; - text-align: center; - z-index: 2; - color: @beige; - - .slot-bar { - display: flex; - flex-wrap: wrap; - gap: 5px; - padding: 5px; - border: 1px solid @color-border; - border-radius: 6px; - z-index: 1; - color: @color-text-emphatic; - width: fit-content; - - .slot { - width: 15px; - height: 10px; - border: 1px solid @color-border; - background: light-dark(@dark-blue-10, @golden-10); - border-radius: 3px; - transition: all 0.3s ease; - cursor: pointer; - - &.large { - width: 20px; - } - - &.filled { - background: light-dark(@dark-blue, @golden); - } - } - - .empty-slot { - width: 15px; - height: 10px; - } - } - .slot-label { - display: flex; - align-items: center; - color: light-dark(@beige, @dark-blue); - background: light-dark(@dark-blue, @golden); - padding: 0 5px; - width: fit-content; - font-weight: bold; - border-radius: 0px 0px 5px 5px; - font-size: var(--font-size-12); - - .label { - padding-right: 5px; - } - - .value { - padding-left: 6px; - border-left: 1px solid light-dark(@beige, @dark-golden); - } - } - } .status-value { - position: absolute; + position: relative; display: flex; padding: 0 5px; font-size: 1.5rem; align-items: center; - width: 140px; + width: 100px; height: 40px; justify-content: center; text-align: center; z-index: 2; color: @beige; + > * { + z-index: 1; + } + input[type='number'] { background: transparent; font-size: 1.5rem; @@ -146,11 +143,11 @@ .progress-bar { position: absolute; appearance: none; - width: 100px; - height: 40px; + width: 100%; + height: 100%; border: 1px solid @color-border; border-radius: 6px; - z-index: 1; + z-index: 0; background: @dark-blue; &::-webkit-progress-bar { @@ -175,4 +172,29 @@ border-radius: 6px; } } + .status-label { + position: relative; + height: 22px; + width: 79px; + background: light-dark(@dark-blue, @golden); + + &.pointy { + clip-path: path('M0 0H79L74 16.5L39 22L4 16.5L0 0Z'); + margin-bottom: -2px; // compensate for pointy bottom so spacing feels more "right" + } + + h4 { + font-weight: bold; + text-align: center; + line-height: 18px; + color: light-dark(@beige, @dark-blue); + } + } } + +// Overrides for sidebar usage. +aside[data-application-part="sidebar"] .resources-section .slot-bar { + display: grid; + grid-template-columns: repeat(6, min-content); + grid-auto-flow: row; +} \ No newline at end of file diff --git a/styles/less/global/sheet.less b/styles/less/global/sheet.less index e3072da1..8381c7c3 100755 --- a/styles/less/global/sheet.less +++ b/styles/less/global/sheet.less @@ -54,7 +54,7 @@ body.game:is(.performance-low, .noblur) { position: relative; min-height: -webkit-fill-available; transition: opacity 0.3s ease; - padding-bottom: 20px; + padding-bottom: 16px; .tab { padding: 0 10px; diff --git a/styles/less/sheets/actors/actor-sheet-shared.less b/styles/less/sheets/actors/actor-sheet-shared.less index 5eb5b43c..3e233013 100644 --- a/styles/less/sheets/actors/actor-sheet-shared.less +++ b/styles/less/sheets/actors/actor-sheet-shared.less @@ -39,6 +39,20 @@ .window-header > .attribution-header-label { margin-right: var(--spacer-4); + pointer-events: none; + } + + .tab-navigation { + margin-bottom: 0; + } + + .tab { + flex: 1; + padding: 0; + overflow: hidden; + .search-section { + padding: 12px 14px var(--spacer-8) 12px; + } } .tab.inventory { @@ -46,7 +60,7 @@ display: grid; grid-template-columns: 1fr 1fr 1fr 1fr; gap: 10px; - padding: 10px 10px 0; + padding: var(--spacer-8) 16px var(--spacer-8) 16px; .input { color: light-dark(@dark, @beige); @@ -57,7 +71,6 @@ .tab.notes.active { padding: 0; margin: 0; - margin-top: -10px; // will be removed once tab-navigation bottom margin is removed on all actor sheets scrollbar-gutter: unset; // Add padding around top level level prosemirrors used for note tabs @@ -66,6 +79,7 @@ .editor-content { scrollbar-gutter: stable; padding-right: @right-padding; + padding-bottom: 4px; } &.inactive { button.toggle { @@ -76,7 +90,7 @@ } } &.active { - padding: 8px 0 4px 16px; + padding: 8px 0 0 16px; } } diff --git a/styles/less/sheets/actors/adversary/effects.less b/styles/less/sheets/actors/adversary/effects.less index 4aa44e51..f489bee6 100644 --- a/styles/less/sheets/actors/adversary/effects.less +++ b/styles/less/sheets/actors/adversary/effects.less @@ -7,9 +7,8 @@ display: flex; flex-direction: column; gap: 10px; - overflow-y: auto; - padding-bottom: 20px; - .with-scroll-shadows(); + padding: 7px calc(12px - var(--scrollbar-width)) 4px 12px; + .stable-scroll-container(); } } } diff --git a/styles/less/sheets/actors/adversary/features.less b/styles/less/sheets/actors/adversary/features.less index 447d050e..49289d36 100644 --- a/styles/less/sheets/actors/adversary/features.less +++ b/styles/less/sheets/actors/adversary/features.less @@ -5,12 +5,8 @@ .application.sheet.daggerheart.actor.dh-style.adversary { .tab.features { .feature-section { - display: flex; - flex-direction: column; - gap: 10px; - overflow-y: auto; - padding-bottom: 20px; - .with-scroll-shadows(); + padding: 16px calc(16px - var(--scrollbar-width)) 4px 16px; + .stable-scroll-container(); } } } diff --git a/styles/less/sheets/actors/adversary/index.less b/styles/less/sheets/actors/adversary/index.less index 28ff9d22..978cf1f8 100644 --- a/styles/less/sheets/actors/adversary/index.less +++ b/styles/less/sheets/actors/adversary/index.less @@ -1,7 +1,6 @@ +@import './sheet.less'; @import './features.less'; @import './header.less'; -@import './sheet.less'; @import './sidebar.less'; @import './effects.less'; @import './notes.less'; - diff --git a/styles/less/sheets/actors/adversary/sheet.less b/styles/less/sheets/actors/adversary/sheet.less index 0bd845fa..1eb7d423 100644 --- a/styles/less/sheets/actors/adversary/sheet.less +++ b/styles/less/sheets/actors/adversary/sheet.less @@ -2,35 +2,37 @@ @import '../../../utils/fonts.less'; .application.sheet.daggerheart.actor.dh-style.adversary { + --sidebar-width: 260px; + .window-content { display: grid; - grid-template-columns: 275px 1fr; + grid-template-columns: var(--sidebar-width) 1fr; grid-template-rows: auto 1fr; height: 100%; width: 100%; padding-bottom: 0; + } - .adversary-sidebar-sheet { - grid-row: 1 / span 2; - grid-column: 1; - overflow: hidden; + .adversary-sidebar-sheet { + grid-row: 1 / span 2; + grid-column: 1; + overflow: hidden; + display: flex; + flex-direction: column; + } + + .adversary-header-sheet { + grid-row: 1; + grid-column: 2; + } + + .tab { + grid-row: 2; + grid-column: 2; + &.active { display: flex; flex-direction: column; - } - - .adversary-header-sheet { - grid-row: 1; - grid-column: 2; - } - - .tab { - grid-row: 2; - grid-column: 2; - &.active { - overflow: hidden; - display: flex; - flex-direction: column; - } + margin: 0 0 10px 0; } } -} +} \ No newline at end of file diff --git a/styles/less/sheets/actors/adversary/sidebar.less b/styles/less/sheets/actors/adversary/sidebar.less index 5db9f5e9..8353d092 100644 --- a/styles/less/sheets/actors/adversary/sidebar.less +++ b/styles/less/sheets/actors/adversary/sidebar.less @@ -22,8 +22,8 @@ .application.sheet.daggerheart.actor.dh-style.adversary { .adversary-sidebar-sheet { - width: 275px; - min-width: 275px; + width: var(--sidebar-width); + min-width: var(--sidebar-width); border-right: 1px solid light-dark(@dark-blue, @golden); .portrait { @@ -52,7 +52,7 @@ } img { - height: 275px; + height: var(--sidebar-width); } .death-roll-btn { @@ -63,7 +63,7 @@ .threshold-section { position: relative; display: flex; - gap: 10px; + gap: 7px; background-color: light-dark(transparent, @dark-blue); color: @color-text-emphatic; padding: 5px 10px; @@ -106,8 +106,8 @@ display: flex; flex-direction: column; top: -20px; - gap: 16px; - margin-bottom: -10px; + gap: 10px; + margin-bottom: -16px; &.pip-display { top: -15px; @@ -120,105 +120,6 @@ .resources-section { display: flex; justify-content: space-evenly; - margin-bottom: 16px; - - .status-bar { - display: flex; - justify-content: center; - position: relative; - width: 100px; - height: 40px; - - .status-label { - position: relative; - top: 40px; - height: 22px; - width: 79px; - clip-path: path('M0 0H79L74 16.5L39 22L4 16.5L0 0Z'); - background: light-dark(@dark-blue, @golden); - - h4 { - font-weight: bold; - text-align: center; - line-height: 18px; - color: light-dark(@beige, @dark-blue); - } - } - .status-value { - position: absolute; - display: flex; - padding: 0 6px; - font-size: 1.5rem; - align-items: center; - width: 100px; - height: 40px; - justify-content: center; - text-align: center; - z-index: 2; - color: @beige; - - input[type='number'] { - background: transparent; - font-size: 1.5rem; - width: 40px; - height: 30px; - text-align: center; - border: none; - outline: 2px solid transparent; - color: @beige; - - &.bar-input { - padding: 0; - color: @beige; - backdrop-filter: none; - background: transparent; - transition: all 0.3s ease; - - &:hover, - &:focus { - background: @semi-transparent-dark-blue; - backdrop-filter: blur(9.5px); - } - } - } - - .bar-label { - width: 40px; - } - } - .progress-bar { - position: absolute; - appearance: none; - width: 100px; - height: 40px; - border: 1px solid @color-border; - border-radius: 6px; - z-index: 1; - background: @dark-blue; - - &::-webkit-progress-bar { - border: none; - background: @dark-blue; - border-radius: 6px; - } - &::-webkit-progress-value { - background: @gradient-hp; - border-radius: 6px; - } - &.stress-color::-webkit-progress-value { - background: @gradient-stress; - border-radius: 6px; - } - &::-moz-progress-bar { - background: @gradient-hp; - border-radius: 6px; - } - &.stress-color::-moz-progress-bar { - background: @gradient-stress; - border-radius: 6px; - } - } - } } .status-section { @@ -256,7 +157,7 @@ .status-label { padding: 2px 10px; width: 100%; - border-radius: 3px; + border-radius: 0 0 3px 3px; background: light-dark(@dark-blue, @golden); h4 { @@ -284,8 +185,8 @@ .shortcut-items-section { overflow-y: hidden; - padding-top: 10px; - padding-bottom: 20px; + padding: 10px 0; + flex: 1; scrollbar-gutter: stable; .with-scroll-shadows(); @@ -313,8 +214,6 @@ } .experience-section { - margin-bottom: 20px; - .title { display: flex; gap: 15px; @@ -331,21 +230,21 @@ gap: 5px; width: 100%; margin-top: 10px; - align-items: center; + align-items: stretch; + padding: 0 4px 0 12px; .experience-row { display: flex; gap: 5px; - width: 250px; align-items: center; justify-content: space-between; .experience-name { - width: 180px; display: flex; align-items: center; text-align: start; font-size: var(--font-size-14); + flex: 1; color: light-dark(@dark, @beige); line-height: 1; } @@ -370,9 +269,7 @@ .reaction-section { display: flex; - padding: 0 10px; - margin-top: 20px; - width: 100%; + margin: 10px; button { height: 40px; diff --git a/styles/less/sheets/actors/character/biography.less b/styles/less/sheets/actors/character/biography.less index 8548a2fb..9782a588 100644 --- a/styles/less/sheets/actors/character/biography.less +++ b/styles/less/sheets/actors/character/biography.less @@ -8,17 +8,14 @@ display: flex; flex-direction: column; gap: 10px; - height: 100%; - overflow-y: auto; - padding-top: 8px; - padding-bottom: 20px; - height: 100%; - .with-scroll-shadows(); + height: 100%; + padding: 12px calc(12px - var(--scrollbar-width)) 4px 12px; + .stable-scroll-container(); } .characteristics-section { gap: 20px; - padding: 0 10px; + padding: 0 4px; } .biography-section { diff --git a/styles/less/sheets/actors/character/effects.less b/styles/less/sheets/actors/character/effects.less index 0ab1007d..8fac301c 100644 --- a/styles/less/sheets/actors/character/effects.less +++ b/styles/less/sheets/actors/character/effects.less @@ -8,9 +8,8 @@ display: flex; flex-direction: column; gap: 10px; - overflow-y: auto; - padding-bottom: 20px; - .with-scroll-shadows(); + padding: 7px calc(12px - var(--scrollbar-width)) 4px 12px; + .stable-scroll-container(); } } } diff --git a/styles/less/sheets/actors/character/features.less b/styles/less/sheets/actors/character/features.less index 52b41826..fcde3e38 100644 --- a/styles/less/sheets/actors/character/features.less +++ b/styles/less/sheets/actors/character/features.less @@ -8,9 +8,8 @@ display: flex; flex-direction: column; gap: 10px; - overflow-y: auto; - padding-bottom: 20px; - .with-scroll-shadows(); + padding: 7px calc(12px - var(--scrollbar-width)) 4px 12px; + .stable-scroll-container(); } } } diff --git a/styles/less/sheets/actors/character/header.less b/styles/less/sheets/actors/character/header.less index 91b3545a..81345715 100644 --- a/styles/less/sheets/actors/character/header.less +++ b/styles/less/sheets/actors/character/header.less @@ -19,16 +19,19 @@ .application.sheet.daggerheart.actor.dh-style.character { .character-header-sheet { - padding: 0 15px; padding-top: var(--header-height); width: 100%; + > *:not(line-div, .tab-navigation) { + padding-left: 15px; + padding-right: 15px; + } + .name-row { display: flex; gap: 6px; align-items: start; justify-content: space-between; - padding: 0; padding-top: 5px; flex: 1; @@ -100,8 +103,8 @@ .character-details { display: flex; justify-content: space-between; - padding: 5px 0; - margin-bottom: 8px; + margin-top: 5px; + margin-bottom: 10px; font-size: var(--font-size-12); color: @color-text-emphatic; @@ -130,7 +133,6 @@ .character-row { display: flex; align-items: center; - padding: 0; margin-bottom: 12px; .resource-section { @@ -218,12 +220,11 @@ .character-traits { display: flex; - padding: 0; margin-bottom: 15px; justify-content: space-between; max-width: 38.5rem; gap: 0.5rem; - padding-left: 0.5rem; + margin-left: 0.5rem; .trait { cursor: pointer; @@ -325,5 +326,9 @@ } } } + + .tab-navigation button[data-action="openSettings"] { + margin-right: 12px; + } } } diff --git a/styles/less/sheets/actors/character/index.less b/styles/less/sheets/actors/character/index.less index edefe0a1..f196d5bf 100644 --- a/styles/less/sheets/actors/character/index.less +++ b/styles/less/sheets/actors/character/index.less @@ -1,8 +1,8 @@ +@import './sheet.less'; @import './biography.less'; @import './effects.less'; @import './features.less'; @import './header.less'; @import './inventory.less'; @import './loadout.less'; -@import './sheet.less'; @import './sidebar.less'; diff --git a/styles/less/sheets/actors/character/inventory.less b/styles/less/sheets/actors/character/inventory.less index fcfbbee9..ce7a8cdb 100644 --- a/styles/less/sheets/actors/character/inventory.less +++ b/styles/less/sheets/actors/character/inventory.less @@ -7,11 +7,9 @@ .items-section { display: flex; flex-direction: column; - gap: 10px; - overflow-y: auto; - margin-top: 20px; - padding-bottom: 20px; - .with-scroll-shadows(); + gap: 10px; + padding: 8px calc(12px - var(--scrollbar-width)) 4px 12px; + .stable-scroll-container(); } } } diff --git a/styles/less/sheets/actors/character/loadout.less b/styles/less/sheets/actors/character/loadout.less index fa3e0176..cb1baaa6 100644 --- a/styles/less/sheets/actors/character/loadout.less +++ b/styles/less/sheets/actors/character/loadout.less @@ -50,11 +50,8 @@ display: flex; flex-direction: column; gap: 10px; - height: 100%; - overflow-y: auto; - margin-top: 20px; - padding-bottom: 20px; - .with-scroll-shadows(); + padding: 8px calc(12px - var(--scrollbar-width)) 4px 12px; + .stable-scroll-container(); } } } diff --git a/styles/less/sheets/actors/character/sheet.less b/styles/less/sheets/actors/character/sheet.less index 68792c99..43401b19 100644 --- a/styles/less/sheets/actors/character/sheet.less +++ b/styles/less/sheets/actors/character/sheet.less @@ -30,7 +30,7 @@ &.active { display: flex; flex-direction: column; - overflow: hidden; + margin: 0 0 10px 0; } } } diff --git a/styles/less/sheets/actors/character/sidebar.less b/styles/less/sheets/actors/character/sidebar.less index c76ee9ff..66f09cfc 100644 --- a/styles/less/sheets/actors/character/sidebar.less +++ b/styles/less/sheets/actors/character/sidebar.less @@ -89,113 +89,13 @@ .resources-section { justify-content: space-around; - margin: 8px 2px 8px 2px; + margin: 8px 2px 0 2px; } } .resources-section { display: flex; justify-content: space-evenly; - margin-bottom: 20px; - - .status-bar { - display: flex; - justify-content: center; - position: relative; - width: 120px; - height: 40px; - - .status-label { - position: relative; - top: 40px; - height: 22px; - width: 79px; - clip-path: path('M0 0H79L74 16.5L39 22L4 16.5L0 0Z'); - background: light-dark(@dark-blue, @golden); - - h4 { - font-weight: bold; - text-align: center; - line-height: 18px; - color: light-dark(@beige, @dark-blue); - } - } - - .status-value { - position: absolute; - display: flex; - padding: 0 5px; - font-size: 1.5rem; - align-items: center; - width: 140px; - height: 40px; - justify-content: center; - text-align: center; - z-index: 2; - color: @beige; - - input[type='number'] { - background: transparent; - font-size: 1.5rem; - width: 40px; - height: 30px; - text-align: center; - border: none; - outline: 2px solid transparent; - color: @beige; - - &.bar-input { - padding: 0; - color: @beige; - backdrop-filter: none; - background: transparent; - transition: all 0.3s ease; - - &:hover, - &:focus { - background: @semi-transparent-dark-blue; - backdrop-filter: blur(9.5px); - } - } - } - - .bar-label { - width: 40px; - } - } - .progress-bar { - position: absolute; - appearance: none; - width: 100px; - height: 40px; - border: 1px solid @color-border; - border-radius: 6px; - z-index: 1; - background: @dark-blue; - - &::-webkit-progress-bar { - border: none; - background: @dark-blue; - border-radius: 6px; - } - &::-webkit-progress-value { - background: @gradient-hp; - border-radius: 6px; - } - &.stress-color::-webkit-progress-value { - background: @gradient-stress; - border-radius: 6px; - } - &::-moz-progress-bar { - background: @gradient-hp; - border-radius: 6px; - } - &.stress-color::-moz-progress-bar { - background: @gradient-stress; - border-radius: 6px; - } - } - } } .status-section { @@ -245,21 +145,21 @@ .status-bar.armor-slots { display: flex; - justify-content: center; - position: relative; width: 95px; - height: 30px; white-space: nowrap; + .status-label { + height: 30px; + } + .status-label { padding: 2px 2px; position: relative; - top: 30px; height: 22px; width: 95px; border-radius: 3px; background: light-dark(@dark-blue, @golden); - clip-path: none; + display: flex; align-items: center; justify-content: center; @@ -291,40 +191,16 @@ } } .slot-value { - position: absolute; - display: flex; - padding: 0 5px; font-size: 1.2rem; - align-items: center; width: 80px; height: 30px; - justify-content: center; - text-align: center; - z-index: 2; color: light-dark(@dark-blue, @beige); flex-direction: column; .slot-bar { - display: flex; - flex-wrap: wrap; - gap: 4px; - padding: 5px; - border: 1px solid @color-border; - border-radius: 6px; - z-index: 1; background: @dark-blue; justify-content: center; - color: @color-text-emphatic; - - .armor-slot { - cursor: pointer; - transition: all 0.3s ease; - font-size: var(--font-size-12); - - .fa-shield-halved { - color: light-dark(@dark-blue-40, @golden-40); - } - } + border-bottom: none; } .slot-label { display: flex; @@ -364,41 +240,21 @@ } } .status-value { - position: absolute; - display: flex; padding: 0 6px; font-size: 1.2rem; - align-items: center; width: 80px; height: 30px; justify-content: center; - text-align: center; - z-index: 2; color: light-dark(@dark-blue, @beige); - border: 1px solid @color-border; - border-bottom: none; - border-radius: 6px 6px 0 0; input[type='number'] { - background: transparent; font-size: 1.2rem; width: 30px; height: 20px; - text-align: center; - border: none; - outline: 2px solid transparent; color: light-dark(@dark-blue, @beige); &.bar-input { - padding: 0; color: light-dark(@dark-blue, @beige); - backdrop-filter: none; - background: transparent; - &:hover, - &:focus { - background: @semi-transparent-dark-blue; - backdrop-filter: blur(9.5px); - } } } @@ -407,32 +263,9 @@ } } .progress-bar { - position: absolute; - appearance: none; - width: 80px; - height: 30px; - border: 1px solid @color-border; - border-radius: 6px; - z-index: 1; background: light-dark(transparent, @dark-blue); border-bottom: none; border-radius: 6px 6px 0 0; - &::-webkit-progress-bar { - border: none; - background: light-dark(transparent, @dark-blue); - } - &::-webkit-progress-value { - background: @gradient-stress; - } - &.stress-color::-webkit-progress-value { - background: @gradient-stress; - } - &::-moz-progress-bar { - background: @gradient-stress; - } - &.stress-color::-moz-progress-bar { - background: @gradient-stress; - } } } diff --git a/styles/less/sheets/actors/companion/details.less b/styles/less/sheets/actors/companion/details.less index 2e43cac4..e10e7680 100644 --- a/styles/less/sheets/actors/companion/details.less +++ b/styles/less/sheets/actors/companion/details.less @@ -1,7 +1,10 @@ @import '../../../utils/colors.less'; @import '../../../utils/fonts.less'; -.application.sheet.daggerheart.actor.dh-style.companion { +.application.sheet.daggerheart.actor.dh-style.companion .tab.details.active { + padding: 12px calc(12px - var(--scrollbar-width)) 4px 12px; + .stable-scroll-container(); + .partner-section, .attack-section, .experience-list { diff --git a/styles/less/sheets/actors/companion/effects.less b/styles/less/sheets/actors/companion/effects.less index c0cac669..ffe66da8 100644 --- a/styles/less/sheets/actors/companion/effects.less +++ b/styles/less/sheets/actors/companion/effects.less @@ -6,9 +6,8 @@ display: flex; flex-direction: column; gap: 10px; - overflow-y: auto; - padding-bottom: 20px; - .with-scroll-shadows(); + padding: 7px calc(12px - var(--scrollbar-width)) 4px 12px; + .stable-scroll-container(); } } } diff --git a/styles/less/sheets/actors/environment/features.less b/styles/less/sheets/actors/environment/features.less index 84cf26f8..c6fe3750 100644 --- a/styles/less/sheets/actors/environment/features.less +++ b/styles/less/sheets/actors/environment/features.less @@ -5,12 +5,8 @@ .application.sheet.daggerheart.actor.dh-style.environment { .tab.features { .feature-section { - display: flex; - flex-direction: column; - gap: 10px; - overflow-y: auto; - padding-bottom: 4px; - .with-scroll-shadows(); + padding: 16px calc(16px - var(--scrollbar-width)) 4px 16px; + .stable-scroll-container(); } } } diff --git a/styles/less/sheets/actors/environment/index.less b/styles/less/sheets/actors/environment/index.less index 211c8e60..df7e6fc2 100644 --- a/styles/less/sheets/actors/environment/index.less +++ b/styles/less/sheets/actors/environment/index.less @@ -1,4 +1,4 @@ +@import './sheet.less'; @import './features.less'; @import './header.less'; @import './potentialAdversaries.less'; -@import './sheet.less'; diff --git a/styles/less/sheets/actors/environment/potentialAdversaries.less b/styles/less/sheets/actors/environment/potentialAdversaries.less index f112c0d2..07dc5f92 100644 --- a/styles/less/sheets/actors/environment/potentialAdversaries.less +++ b/styles/less/sheets/actors/environment/potentialAdversaries.less @@ -6,9 +6,8 @@ display: flex; flex-direction: column; gap: 10px; - overflow-y: auto; - padding-bottom: 4px; - .with-scroll-shadows(); + padding: 7px calc(12px - var(--scrollbar-width)) 4px 12px; + .stable-scroll-container(); } } } diff --git a/styles/less/sheets/actors/environment/sheet.less b/styles/less/sheets/actors/environment/sheet.less index 2d9cc188..ef9d542a 100644 --- a/styles/less/sheets/actors/environment/sheet.less +++ b/styles/less/sheets/actors/environment/sheet.less @@ -14,9 +14,7 @@ .application.sheet.daggerheart.actor.dh-style.environment { .tab { - flex: 1; overflow-y: auto; - &.active { overflow: hidden; display: flex; diff --git a/styles/less/sheets/actors/npc/features.less b/styles/less/sheets/actors/npc/features.less index a579d9f8..f68df8a8 100644 --- a/styles/less/sheets/actors/npc/features.less +++ b/styles/less/sheets/actors/npc/features.less @@ -7,12 +7,8 @@ } .feature-section { - display: flex; - flex-direction: column; - gap: 10px; - overflow-y: auto; - padding-bottom: 4px; - .with-scroll-shadows(); + padding: 16px calc(16px - var(--scrollbar-width)) 4px 16px; + .stable-scroll-container(); } } } diff --git a/styles/less/sheets/actors/party/party-members.less b/styles/less/sheets/actors/party/party-members.less index dc464291..ca384322 100644 --- a/styles/less/sheets/actors/party/party-members.less +++ b/styles/less/sheets/actors/party/party-members.less @@ -1,295 +1,286 @@ -@import '../../../utils/colors.less'; -@import '../../../utils/fonts.less'; -@import '../../../utils/mixin.less'; - -.application.sheet.daggerheart.actor.dh-style.party .tab.partyMembers { - overflow: auto; - - .actors-list { - display: flex; - flex-direction: column; - gap: 8px; - align-items: stretch; - width: 100%; - - .actor-resources { - display: grid; - grid-template: - "img header" min-content - "img body" 1fr - / 7.5rem 1fr; - gap: 6px; - column-gap: 12px; - padding: 6px; - background-color: light-dark(@dark-blue-10, @golden-10); - - .actor-img-frame { - grid-area: img; - width: 7.375rem; - height: 7.375rem; - position: relative; - - .actor-img { - object-fit: cover; - object-position: top center; - border-radius: 6px; - width: 100%; - height: 100%; - } - - .equipped-weapons { - position: absolute; - top: -2px; - left: -3px; - display: flex; - flex-direction: column; - gap: 1px; - img { - border-radius: 50%; - width: 24px; - height: 24px; - border: 1px solid @color-border; - object-fit: cover; - } - } - - .evasion { - position: absolute; - top: 1px; - right: 1px; - width: 1.75rem; - height: 1.75rem; - background: url('../assets/svg/trait-shield.svg') no-repeat; - background-size: 100%; - color: var(--color-light-1); - font-size: var(--font-size-14); - font-weight: 700; - display: flex; - align-items: center; - justify-content: center; - } - - .threshold-section { - position: absolute; - left: 0; - right: 0; - bottom: -2px; - margin: auto; - - display: flex; - gap: 4px; - background-color: light-dark(var(--color-light-1), @dark-blue); - color: @color-text-emphatic; - padding: 4px 6px; - border: 1px solid @color-border; - border-radius: 3px; - align-items: baseline; - width: fit-content; - - h4 { - font-weight: bold; - text-transform: uppercase; - white-space: nowrap; - - &.threshold-label { - font-size: var(--font-size-10); - color: @color-text-emphatic; - } - - &.threshold-value { - font-size: var(--font-size-11); - color: light-dark(@dark, @beige); - } - } - } - } - - header { - grid-area: header; - display: grid; - grid-template: - "name hope" min-content - "subtitle subtitle" min-content - / 1fr min-content; - - .actor-name { - width: 100%; - z-index: 1; - font-size: var(--font-size-20); - color: @color-text-emphatic; - font-weight: bold; - } - - .delete-icon { - font-size: 0.75em; - } - - .subtitle { - grid-area: subtitle; - font-size: var(--font-size-14); - } - - .hope-section { - display: flex; - background-color: light-dark(transparent, @dark-blue); - color: @color-text-emphatic; - padding: 3px 6px; - border: 1px solid @color-border; - border-radius: 3px; - align-items: center; - width: fit-content; - margin-left: auto; - - h4 { - font-size: var(--font-size-12); - font-weight: bold; - text-transform: uppercase; - color: @color-text-emphatic; - margin-right: 3px; - } - - .hope-value { - display: flex; - cursor: pointer; - font-size: var(--font-size-12); - margin-left: 1px; - } - } - } - - .body { - grid-area: body; - display: flex; - align-items: start; - justify-content: space-between; - } - - .resources { - display: flex; - flex-direction: column; - gap: 4px; - - .slot-section { - display: flex; - flex-direction: row; - align-items: stretch; - - .slot-label { - display: flex; - align-items: center; - color: light-dark(@beige, @dark-blue); - background: light-dark(@dark-blue, @golden); - padding: 0 4px; - width: fit-content; - font-weight: bold; - border-radius: 6px 0px 0px 6px; - font-size: var(--font-size-12); - white-space: nowrap; - - .label { - padding-right: 2px; - } - - .value { - font-variant-numeric: tabular-nums; - .current { - display: inline-block; - text-align: end; - width: 2ch; - } - .max { - display: inline-block; - text-align: start; - width: 2ch; - } - } - } - - .slot-bar { - display: flex; - align-items: center; - flex-wrap: wrap; - gap: 4px; - - background-color: light-dark(@dark-blue-10, @dark-blue); - color: @color-text-emphatic; - padding: 2px 5px; - border: 1px solid @color-border; - border-radius: 0 6px 6px 0; - width: fit-content; - min-height: 22px; - - .armor-slot { - cursor: pointer; - transition: all 0.3s ease; - font-size: var(--font-size-12); - - .fa-shield-halved { - color: light-dark(@dark-blue-40, @golden-40); - } - } - - .slot { - width: 16px; - height: 10px; - border: 1px solid @color-border; - background: light-dark(@dark-blue-10, @golden-10); - border-radius: 3px; - transition: all 0.3s ease; - cursor: pointer; - - &.filled { - background: light-dark(@dark-blue, @golden); - } - } - } - } - } - - .traits { - background-color: light-dark(@dark-blue-10, @dark-blue); - border: 1px solid @color-border; - border-radius: 6px; - display: grid; - grid-template-columns: 1fr 1fr; - font-size: var(--font-size-12); - padding: 3px 4px; - gap: 3px 7px; - .trait { - display: flex; - justify-content: space-between; - gap: 3px; - .label { - color: @color-text-emphatic; - } - .value { - font-weight: 600; - } - } - } - } - } - - .actors-list.limited { - .actor-resources { - display: flex; - align-items: center; - } - .actor-img-frame { - width: 3rem; - height: 3rem; - } - } - - .actors-dragger { - display: flex; - align-items: center; - justify-content: center; - box-sizing: border-box; - width: 100%; - height: 40px; - border: 1px dashed light-dark(@dark-blue-50, @beige-50); - border-radius: 3px; - color: light-dark(@dark-blue-50, @beige-50); - } -} +@import '../../../utils/colors.less'; +@import '../../../utils/fonts.less'; +@import '../../../utils/mixin.less'; + +.application.sheet.daggerheart.actor.dh-style.party .tab.partyMembers { + padding: 12px calc(12px - var(--scrollbar-width)) 4px 12px; + .stable-scroll-container(); + + .actions-section { + display: flex; + align-items: center; + justify-content: center; + padding: 10px; + margin-bottom: 10px; + gap: 20px; + background-color: light-dark(@dark-blue-10, @golden-10); + + button { + span { + font-size: 12px; + } + } + + .active-action { + animation: glow 0.75s infinite alternate; + } + } + + .actors-list { + display: flex; + flex-direction: column; + gap: 8px; + align-items: stretch; + width: 100%; + + .actor-resources { + display: grid; + grid-template: + "img header" min-content + "img body" 1fr + / 7.5rem 1fr; + gap: 6px; + column-gap: 12px; + padding: 6px; + background-color: light-dark(@dark-blue-10, @golden-10); + + .actor-img-frame { + grid-area: img; + width: 7.375rem; + height: 7.375rem; + position: relative; + + .actor-img { + object-fit: cover; + object-position: top center; + border-radius: 6px; + width: 100%; + height: 100%; + } + + .equipped-weapons { + position: absolute; + top: -2px; + left: -3px; + display: flex; + flex-direction: column; + gap: 1px; + img { + border-radius: 50%; + width: 24px; + height: 24px; + border: 1px solid @color-border; + object-fit: cover; + } + } + + .evasion { + position: absolute; + top: 1px; + right: 1px; + width: 1.75rem; + height: 1.75rem; + background: url('../assets/svg/trait-shield.svg') no-repeat; + background-size: 100%; + color: var(--color-light-1); + font-size: var(--font-size-14); + font-weight: 700; + display: flex; + align-items: center; + justify-content: center; + } + + .threshold-section { + position: absolute; + left: 0; + right: 0; + bottom: -2px; + margin: auto; + + display: flex; + gap: 4px; + background-color: light-dark(var(--color-light-1), @dark-blue); + color: @color-text-emphatic; + padding: 4px 6px; + border: 1px solid @color-border; + border-radius: 3px; + align-items: baseline; + width: fit-content; + + h4 { + font-weight: bold; + text-transform: uppercase; + white-space: nowrap; + + &.threshold-label { + font-size: var(--font-size-10); + color: @color-text-emphatic; + } + + &.threshold-value { + font-size: var(--font-size-11); + color: light-dark(@dark, @beige); + } + } + } + } + + header { + grid-area: header; + display: grid; + grid-template: + "name hope" min-content + "subtitle subtitle" min-content + / 1fr min-content; + + .actor-name { + width: 100%; + z-index: 1; + font-size: var(--font-size-20); + color: @color-text-emphatic; + font-weight: bold; + } + + .delete-icon { + font-size: 0.75em; + } + + .subtitle { + grid-area: subtitle; + font-size: var(--font-size-14); + } + + .hope-section { + display: flex; + background-color: light-dark(transparent, @dark-blue); + color: @color-text-emphatic; + padding: 3px 6px; + border: 1px solid @color-border; + border-radius: 3px; + align-items: center; + width: fit-content; + margin-left: auto; + + h4 { + font-size: var(--font-size-12); + font-weight: bold; + text-transform: uppercase; + color: @color-text-emphatic; + margin-right: 3px; + } + + .hope-value { + display: flex; + cursor: pointer; + font-size: var(--font-size-12); + margin-left: 1px; + } + } + } + + .body { + grid-area: body; + display: flex; + align-items: start; + justify-content: space-between; + } + + .resources { + display: flex; + flex-direction: column; + gap: 4px; + + .slot-section { + display: flex; + flex-direction: row; + align-items: stretch; + + .slot-label { + display: flex; + align-items: center; + color: light-dark(@beige, @dark-blue); + background: light-dark(@dark-blue, @golden); + padding: 0 4px; + width: fit-content; + font-weight: bold; + border-radius: 6px 0px 0px 6px; + font-size: var(--font-size-12); + white-space: nowrap; + + .label { + padding-right: 2px; + } + + .value { + font-variant-numeric: tabular-nums; + .current { + display: inline-block; + text-align: end; + width: 2ch; + } + .max { + display: inline-block; + text-align: start; + width: 2ch; + } + } + } + + .slot-bar { + align-items: center; + flex-wrap: wrap; + background-color: light-dark(@dark-blue-10, @dark-blue); + padding: 2px 5px; + border-radius: 0 6px 6px 0; + width: fit-content; + } + } + } + + .traits { + background-color: light-dark(@dark-blue-10, @dark-blue); + border: 1px solid @color-border; + border-radius: 6px; + display: grid; + grid-template-columns: 1fr 1fr; + font-size: var(--font-size-12); + padding: 3px 4px; + gap: 3px 7px; + .trait { + display: flex; + justify-content: space-between; + gap: 3px; + .label { + color: @color-text-emphatic; + } + .value { + font-weight: 600; + } + } + } + } + } + + .actors-list.limited { + .actor-resources { + display: flex; + align-items: center; + } + .actor-img-frame { + width: 3rem; + height: 3rem; + } + } + + .actors-dragger { + display: flex; + align-items: center; + justify-content: center; + box-sizing: border-box; + width: 100%; + height: 40px; + border: 1px dashed light-dark(@dark-blue-50, @beige-50); + border-radius: 3px; + color: light-dark(@dark-blue-50, @beige-50); + } +} diff --git a/styles/less/sheets/actors/party/sheet.less b/styles/less/sheets/actors/party/sheet.less index 852b6cfc..bf589696 100644 --- a/styles/less/sheets/actors/party/sheet.less +++ b/styles/less/sheets/actors/party/sheet.less @@ -17,35 +17,8 @@ }); .application.sheet.daggerheart.actor.dh-style.party { - .tab { - flex: 1; - overflow-y: auto; - scrollbar-gutter: stable; - - &.active { - overflow: auto; - display: flex; - flex-direction: column; - } - - .actions-section { - display: flex; - align-items: center; - justify-content: center; - padding: 10px; - margin-bottom: 10px; - gap: 20px; - background-color: light-dark(@dark-blue-10, @golden-10); - - button { - span { - font-size: 12px; - } - } - - .active-action { - animation: glow 0.75s infinite alternate; - } - } + .tab.active { + display: flex; + flex-direction: column; } } diff --git a/styles/less/utils/mixin.less b/styles/less/utils/mixin.less index e2ef85ef..2ce85166 100644 --- a/styles/less/utils/mixin.less +++ b/styles/less/utils/mixin.less @@ -174,10 +174,10 @@ --fade-start: 0; } 10%, 100% { - --fade-start: 12px; + --fade-start: 14px; } 0%, 90% { - --fade-end: 12px; + --fade-end: 14px; } 100% { --fade-end: 0; @@ -198,3 +198,9 @@ transparent 100% ); } + +.stable-scroll-container() { + overflow-y: auto; + scrollbar-gutter: stable; + .with-scroll-shadows(); +} \ No newline at end of file diff --git a/system.json b/system.json index 0ecc2e75..d754b8bf 100644 --- a/system.json +++ b/system.json @@ -2,7 +2,7 @@ "id": "daggerheart", "title": "Daggerheart", "description": "An unofficial implementation of the Daggerheart system", - "version": "2.4.2", + "version": "2.5.0", "compatibility": { "minimum": "14.364", "verified": "14.364", @@ -10,7 +10,7 @@ }, "url": "https://github.com/Foundryborne/daggerheart", "manifest": "https://raw.githubusercontent.com/Foundryborne/daggerheart/v14/system.json", - "download": "https://github.com/Foundryborne/daggerheart/releases/download/2.4.2/system.zip", + "download": "https://github.com/Foundryborne/daggerheart/releases/download/2.5.0/system.zip", "authors": [ { "name": "WBHarry" diff --git a/templates/actionTypes/areas.hbs b/templates/actionTypes/areas.hbs index 2e010c72..662716f5 100644 --- a/templates/actionTypes/areas.hbs +++ b/templates/actionTypes/areas.hbs @@ -11,9 +11,10 @@
- {{formField ../fields.type value=area.type name=(concat "areas." index ".type") localize=true}} - {{formField ../fields.shape value=area.shape name=(concat "areas." index ".shape") localize=true}} - {{formField ../fields.size value=area.size name=(concat "areas." index ".size") localize=true}} + {{formField ../fields.type value=area.type name=(concat "areas." index ".type") localize=true blank=false}} + {{formField ../fields.shape value=area.shape name=(concat "areas." index ".shape") localize=true blank=false}} + {{formField ../fields.size value=area.size name=(concat "areas." index ".size") localize=true blank=false}} + {{formField ../fields.hasHole value=area.hasHole name=(concat "areas." index ".hasHole") localize=true classes="auto-sized" }}
diff --git a/templates/sheets/actors/adversary/features.hbs b/templates/sheets/actors/adversary/features.hbs index 3b495e74..05e95737 100644 --- a/templates/sheets/actors/adversary/features.hbs +++ b/templates/sheets/actors/adversary/features.hbs @@ -1,14 +1,15 @@
-
- {{> 'daggerheart.inventory-items' - title=tabs.features.label - type='feature' - collection=@root.features - hideContextMenu=true - hideModifyControls=true - canCreate=@root.editable - showActions=@root.editable - }} +
+ {{#each @root.features as |item|}} + {{> "daggerheart.inventory-item" + item=item + type="feature" + actorType=@root.document.type + hideContextMenu=true + hideModifyControls=true + showActions=@root.editable + }} + {{/each}}
\ No newline at end of file diff --git a/templates/sheets/actors/adversary/sidebar.hbs b/templates/sheets/actors/adversary/sidebar.hbs index d2fca5f1..213c96ee 100644 --- a/templates/sheets/actors/adversary/sidebar.hbs +++ b/templates/sheets/actors/adversary/sidebar.hbs @@ -1,100 +1,102 @@ -