Compare commits

...

5 commits

Author SHA1 Message Date
Carlos Fernandez
eccab6ad64
[UI] Make sheet tab scrollsbars stable, remove fieldsets, and adjust padding (#1944)
Some checks failed
Project CI / build (24.x) (push) Has been cancelled
* Remove certain fieldsets and adjust scrollsbars

* Also remove fieldset for npc features and fix padding issue in full screen notes
2026-07-03 11:32:51 +02:00
WBHarry
1dcfc92a03 Raised version 2026-07-03 11:16:55 +02:00
WBHarry
98fedf3990
[Feature] Transform Linked Actors (#2046)
* Added ability to transform linked actors
* Updated to use the utils getWorldActor function
* Animate change and fix error when converting to unlinked
---------

Co-authored-by: Carlos Fernandez <cfern1990@gmail.com>
2026-07-03 11:14:25 +02:00
Gergely Bräutigam
44dd3bd2e1
[Feature] add resources to weapons (#2058)
* feat: add resources to weapons

Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com>

* remove sidebar resource view

Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com>

---------

Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com>
2026-07-03 11:05:28 +02:00
Carlos Fernandez
b3d96c25d9
Fix adjusting tiers of higher level creatures and an error that may occur with hordes (#2059) 2026-07-03 10:37:43 +02:00
45 changed files with 450 additions and 366 deletions

View file

@ -2,11 +2,17 @@
## Table of Contents ## Table of Contents
- [Overview](#overview) - [Foundryborne Daggerheart](#foundryborne-daggerheart)
- [User Install Guide](#user-install) - [Table of Contents](#table-of-contents)
- [Documentation](#documentation) - [Overview](#overview)
- [Developer Setup](#development-setup) - [User Install](#user-install)
- [Contribution Info](#contributing) - [Documentation](#documentation)
- [Development Setup](#development-setup)
- [Available Scripts](#available-scripts)
- [Notes](#notes)
- [Contributing](#contributing)
- [AI Policy](#ai-policy)
- [Disclaimer](#disclaimer)
## Overview ## 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. 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 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. 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.

23
daggerheart.d.ts vendored
View file

@ -4,6 +4,15 @@ import '@common/primitives/global.mjs';
import Canvas from '@client/canvas/board.mjs'; import Canvas from '@client/canvas/board.mjs';
import { ResourceUpdateMap } from './module/data/action/baseAction.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 // Foundry's use of `Object.assign(globalThis) means many globally available objects are not read as such
// This declare global hopefully fixes that // 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 // 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; 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
};
}
}

View file

@ -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", "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.", "canvasError": "There is no active scene.",
"prototypeError": "You can only use a transform action from a Token", "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" "actorLinkError": "You cannot transform a token with Actor Link set to true"
} }
}, },

View file

@ -7,7 +7,7 @@ export default class AdversarySheet extends DHBaseActorSheet {
/** @inheritDoc */ /** @inheritDoc */
static DEFAULT_OPTIONS = { static DEFAULT_OPTIONS = {
classes: ['adversary'], classes: ['adversary'],
position: { width: 645, height: 760 }, position: { width: 645, height: 750 },
window: { resizable: true }, window: { resizable: true },
actions: { actions: {
toggleHitPoints: AdversarySheet.#toggleHitPoints, toggleHitPoints: AdversarySheet.#toggleHitPoints,

View file

@ -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() { get element() {
return document.body.querySelector('.daggerheart.dh-style.effects-display'); return document.body.querySelector('.daggerheart.dh-style.effects-display');
} }

View file

@ -114,7 +114,10 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
return this._id; 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() { get isOwner() {
return this.item?.isOwner ?? true; return this.item?.isOwner ?? true;
} }
@ -143,6 +146,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
/** /**
* Return the first Actor parent found. * Return the first Actor parent found.
* @returns {DhpActor | null}
*/ */
get actor() { get actor() {
return this.item instanceof DhpActor 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. * 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. * An action is usable on any actor type. For example, an adversary might have a base attack action.
* @returns {boolean}
*/ */
get usable() { get usable() {
const actor = this.actor; const actor = this.actor;

View file

@ -2,8 +2,14 @@ import { calculateExpectedValue, parseTermsFromSimpleFormula } from '../../helpe
import { adversaryExpectedDamage, adversaryScalingData } from '../../config/actorConfig.mjs'; import { adversaryExpectedDamage, adversaryScalingData } from '../../config/actorConfig.mjs';
import { parseInlineParams } from '../../enrichers/parser.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) { export function getTierAdjustedAdversary(source, tier) {
const currentTier = source.tier ?? 1; const currentTier = source.system.tier ?? 1;
/** @type {(2 | 3 | 4)[]} */ /** @type {(2 | 3 | 4)[]} */
const tiers = new Array(Math.abs(tier - currentTier)) 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" // Store initial attack damage for abilities that have you deal a "standard attack"
const initialAttack = { const initialAttack = {
type: source.system.attack.damage?.parts.hitPoints?.type?.toSorted(), 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. // Update damage of base attack.
@ -45,9 +51,9 @@ export function getTierAdjustedAdversary(source, tier) {
for (const property of ['value', 'valueAlt']) { for (const property of ['value', 'valueAlt']) {
const data = damage.parts.hitPoints[property]; const data = damage.parts.hitPoints[property];
const previousFormula = getDamagePartsFormula(data); const previousFormula = getFormula(data);
const { value, formula } = calculateAdjustedDamage(previousFormula, 'attack', damageMeta); const value = calculateAdjustedDamage(previousFormula, 'attack', damageMeta);
applyAdjustedDamage(data, value, formula); applyAdjustedDamage(data, value);
} }
} catch (err) { } catch (err) {
ui.notifications.warn('Failed to convert attack damage of adversary'); ui.notifications.warn('Failed to convert attack damage of adversary');
@ -65,7 +71,7 @@ export function getTierAdjustedAdversary(source, tier) {
if (!formula) return match; if (!formula) return match;
try { try {
const newFormula = calculateAdjustedDamage(formula, 'action', damageMeta)?.formula; const newFormula = getFormula(calculateAdjustedDamage(formula, 'action', damageMeta));
descriptionFormulas.push(formula); descriptionFormulas.push(formula);
return match.replace(formula, newFormula); return match.replace(formula, newFormula);
} catch { } catch {
@ -82,15 +88,15 @@ export function getTierAdjustedAdversary(source, tier) {
const result = []; const result = [];
for (const property of ['value', 'valueAlt']) { for (const property of ['value', 'valueAlt']) {
const { [property]: data, type: damageType } = action.damage.parts.hitPoints; const { [property]: data, type: damageType } = action.damage.parts.hitPoints;
const previousFormula = getDamagePartsFormula(data); const previousFormula = getFormula(data);
const isActuallyAttack = const isActuallyAttack =
previousFormula === initialAttack.value && previousFormula === initialAttack.value &&
foundry.utils.equals(damageType.toSorted(), initialAttack.type) && foundry.utils.equals(damageType.toSorted(), initialAttack.type) &&
!descriptionFormulas.includes(previousFormula); !descriptionFormulas.includes(previousFormula);
const type = isActuallyAttack ? 'attack' : 'action'; const type = isActuallyAttack ? 'attack' : 'action';
const { value, formula } = calculateAdjustedDamage(previousFormula, type, damageMeta); const value = calculateAdjustedDamage(previousFormula, type, damageMeta);
applyAdjustedDamage(data, value, formula); applyAdjustedDamage(data, value);
result.push({ previousFormula, formula }); result.push({ previousFormula, formula: getFormula(value) });
} }
// Override text in the description with those values // Override text in the description with those values
@ -189,24 +195,30 @@ function calculateAdjustedDamage(formula, type, { currentDamageRange, newDamageR
value.bonus = Math.round(expected - getBaseAverage()); value.bonus = Math.round(expected - getBaseAverage());
} }
const newFormula = [value.diceQuantity ? `${value.diceQuantity}d${value.faces}` : null, value.bonus] return value;
.filter(p => !!p)
.join('+');
return { value, formula: newFormula };
} }
function getDamagePartsFormula(data) { /**
return data.custom.enabled * Get formula from either damage parts *or* a simple formula object.
? data.custom.formula * @returns {string} the new formula data
: [data.flatMultiplier ? `${data.flatMultiplier}${data.dice}` : 0, data.bonus ?? 0].filter(p => !!p).join('+'); */
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. * Updates damage to reflect a specific value.
* @throws if damage structure is invalid for conversion * @param {object} diceData
* @returns the converted formula and value as a simplified term, or null if it doesn't deal HP damage * @param {object} value
*/ */
function applyAdjustedDamage(diceData, value, formula) { function applyAdjustedDamage(diceData, value) {
if (value.diceQuantity) { if (value.diceQuantity) {
diceData.custom.enabled = false; diceData.custom.enabled = false;
diceData.bonus = value.bonus; diceData.bonus = value.bonus;
@ -214,6 +226,6 @@ function applyAdjustedDamage(diceData, value, formula) {
diceData.flatMultiplier = value.diceQuantity; diceData.flatMultiplier = value.diceQuantity;
} else if (!value.diceQuantity) { } else if (!value.diceQuantity) {
diceData.custom.enabled = true; diceData.custom.enabled = true;
diceData.custom.formula = formula; diceData.custom.formula = getFormula(value);
} }
} }

View file

@ -1,5 +1,11 @@
import { getWorldActor } from '../../../helpers/utils.mjs';
const fields = foundry.data.fields; const fields = foundry.data.fields;
/**
* @import DHSummonAction from '../../action/summonAction.mjs'
*/
export default class DHSummonField extends fields.SchemaField { export default class DHSummonField extends fields.SchemaField {
/** /**
* Action Workflow order * Action Workflow order
@ -20,6 +26,11 @@ export default class DHSummonField extends fields.SchemaField {
super(transformFields, options, context); 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() { static async execute() {
if (!this.transform.actorUUID) { if (!this.transform.actorUUID) {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.ACTIONS.TYPES.transform.noTransformActor')); ui.notifications.warn(game.i18n.localize('DAGGERHEART.ACTIONS.TYPES.transform.noTransformActor'));
@ -37,26 +48,37 @@ export default class DHSummonField extends fields.SchemaField {
return false; return false;
} }
if (this.actor.prototypeToken.actorLink) { const activeTokens = this.actor.getActiveTokens(false, true);
ui.notifications.warn(game.i18n.localize('DAGGERHEART.ACTIONS.TYPES.transform.actorLinkError')); 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; return false;
} }
if (!this.actor.token) { if (!token) {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.ACTIONS.TYPES.transform.prototypeError')); ui.notifications.warn(game.i18n.localize('DAGGERHEART.ACTIONS.TYPES.transform.prototypeError'));
return false; 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 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; const tokenSize = actor?.system.metadata.usesSize ? tokenSizes[actor.system.size] : actor.prototypeToken.width;
await this.actor.token.update( // Update token. Avoid using recursive: false, since that prevents animations
{ ...actor.prototypeToken.toJSON(), actorId: actor.id, width: tokenSize, height: tokenSize }, await token.update(
{ diff: false, recursive: false, noHook: true } { ...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 }); 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) { if (!this.transform.resourceRefresh.hitPoints) {
marks.hitPoints = Math.min( marks.hitPoints = Math.min(
this.actor.system.resources.hitPoints.value, 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) { if (!this.transform.resourceRefresh.stress) {
marks.stress = Math.min( marks.stress = Math.min(
this.actor.system.resources.stress.value, 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) { if (marks.hitPoints || marks.stress) {
this.actor.token.actor.update({ token.actor.update({
'system.resources': { 'system.resources': {
hitPoints: { value: marks.hitPoints }, hitPoints: { value: marks.hitPoints },
stress: { value: marks.stress } stress: { value: marks.stress }
@ -84,20 +106,9 @@ export default class DHSummonField extends fields.SchemaField {
const prevPosition = { ...this.actor.sheet.position }; const prevPosition = { ...this.actor.sheet.position };
this.actor.sheet.close(); this.actor.sheet.close();
this.actor.token.actor.sheet.render({ force: true, position: prevPosition }); token.actor.sheet.render({ force: true, position: prevPosition });
} if (token.object.controlled) {
ui.effectsDisplay.refresh();
/* 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;
} }
const worldActor = await game.system.api.documents.DhpActor.create(baseActor.toObject());
return worldActor;
} }
} }

View file

@ -9,7 +9,8 @@ export default class DHWeapon extends AttachableItem {
type: 'weapon', type: 'weapon',
hasDescription: true, hasDescription: true,
isInventoryItem: true, isInventoryItem: true,
hasActions: true hasActions: true,
hasResource: true
}); });
} }

View file

@ -3,17 +3,26 @@ import DHItem from './item.mjs';
import BaseDataItem from '../data/item/base.mjs'; import BaseDataItem from '../data/item/base.mjs';
import DhActiveEffect from './activeEffect.mjs'; import DhActiveEffect from './activeEffect.mjs';
import EmbeddedCollection from '@common/abstract/embedded-collection.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' { declare module './actor.mjs' {
export default interface DhpActor<T extends BaseDataActor = BaseDataActor> { export default interface DhpActor<T extends BaseDataActor = BaseDataActor> extends Actor {
system: T; system: T;
items: EmbeddedCollection<DHItem>; items: EmbeddedCollection<DHItem>;
effects: EmbeddedCollection<DhActiveEffect>; effects: EmbeddedCollection<DhActiveEffect>;
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' { declare module './item.mjs' {
export default interface DHItem<T extends BaseDataItem = BaseDataItem> { export default interface DHItem<T extends BaseDataItem = BaseDataItem> extends Item {
parent: DhpActor; parent: DhpActor;
actor: DhpActor; actor: DhpActor;
system: T; system: T;

View file

@ -34,12 +34,14 @@ export default class DhpActor extends Actor {
super.prepareData(); super.prepareData();
// Update effects if it is the user's character or is controlled // 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); const controlled = canvas.tokens.controlled.some(t => t.actor === this);
if (game.user.character === this || controlled) { if (game.user.character === this || controlled) {
ui.effectsDisplay.render(); ui.effectsDisplay.refresh();
} }
} }, 0);
} }
/* -------------------------------------------- */ /* -------------------------------------------- */

View file

@ -38,7 +38,8 @@ export default class DHToken extends CONFIG.Token.documentClass {
tokens.filter(x => x.actor).map(x => x.actor) tokens.filter(x => x.actor).map(x => x.actor)
); );
} }
super.createCombatants(tokens, combat ?? {});
await super.createCombatants(tokens, combat ?? {});
} }
/**@inheritdoc */ /**@inheritdoc */

View file

@ -1,6 +1,10 @@
import { diceTypes, getDiceSoNicePresets, getDiceSoNicePreset, range } from '../config/generalConfig.mjs'; import { diceTypes, getDiceSoNicePresets, getDiceSoNicePreset, range } from '../config/generalConfig.mjs';
import Tagify from '@yaireo/tagify'; import Tagify from '@yaireo/tagify';
/**
* @import DhpActor from '../documents/actor.mjs';
*/
export const capitalize = string => { export const capitalize = string => {
return string.charAt(0).toUpperCase() + string.slice(1); return string.charAt(0).toUpperCase() + string.slice(1);
}; };
@ -891,6 +895,13 @@ export function shouldUseHopeFearAutomation(options = { gmAsPlayer: true }) {
return (!game.user.isGM || options.gmAsPlayer) ? hopeFear.players : hopeFear.gm; 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<DhpActor>} a world actor
*/
export async function getWorldActor(baseActor) { export async function getWorldActor(baseActor) {
if (baseActor.inCompendium) { if (baseActor.inCompendium) {
const worldActorCandidates = game.actors.filter(x => const worldActorCandidates = game.actors.filter(x =>

View file

@ -261,7 +261,7 @@
fieldset { fieldset {
align-items: center; align-items: center;
margin-top: 5px; margin: 5px 0 0 0;
border-radius: 6px; border-radius: 6px;
border-color: @color-fieldset-border; border-color: @color-fieldset-border;
padding-inline: 0.625rem; padding-inline: 0.625rem;

View file

@ -54,7 +54,7 @@ body.game:is(.performance-low, .noblur) {
position: relative; position: relative;
min-height: -webkit-fill-available; min-height: -webkit-fill-available;
transition: opacity 0.3s ease; transition: opacity 0.3s ease;
padding-bottom: 20px; padding-bottom: 16px;
.tab { .tab {
padding: 0 10px; padding: 0 10px;

View file

@ -39,6 +39,20 @@
.window-header > .attribution-header-label { .window-header > .attribution-header-label {
margin-right: var(--spacer-4); 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 { .tab.inventory {
@ -46,7 +60,7 @@
display: grid; display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr; grid-template-columns: 1fr 1fr 1fr 1fr;
gap: 10px; gap: 10px;
padding: 10px 10px 0; padding: var(--spacer-8) 16px var(--spacer-8) 16px;
.input { .input {
color: light-dark(@dark, @beige); color: light-dark(@dark, @beige);
@ -57,7 +71,6 @@
.tab.notes.active { .tab.notes.active {
padding: 0; padding: 0;
margin: 0; margin: 0;
margin-top: -10px; // will be removed once tab-navigation bottom margin is removed on all actor sheets
scrollbar-gutter: unset; scrollbar-gutter: unset;
// Add padding around top level level prosemirrors used for note tabs // Add padding around top level level prosemirrors used for note tabs
@ -66,6 +79,7 @@
.editor-content { .editor-content {
scrollbar-gutter: stable; scrollbar-gutter: stable;
padding-right: @right-padding; padding-right: @right-padding;
padding-bottom: 4px;
} }
&.inactive { &.inactive {
button.toggle { button.toggle {
@ -76,7 +90,7 @@
} }
} }
&.active { &.active {
padding: 8px 0 4px 16px; padding: 8px 0 0 16px;
} }
} }

View file

@ -7,9 +7,8 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 10px; gap: 10px;
overflow-y: auto; padding: 7px calc(12px - var(--scrollbar-width)) 4px 12px;
padding-bottom: 20px; .stable-scroll-container();
.with-scroll-shadows();
} }
} }
} }

View file

@ -5,12 +5,8 @@
.application.sheet.daggerheart.actor.dh-style.adversary { .application.sheet.daggerheart.actor.dh-style.adversary {
.tab.features { .tab.features {
.feature-section { .feature-section {
display: flex; padding: 16px calc(16px - var(--scrollbar-width)) 4px 16px;
flex-direction: column; .stable-scroll-container();
gap: 10px;
overflow-y: auto;
padding-bottom: 20px;
.with-scroll-shadows();
} }
} }
} }

View file

@ -30,9 +30,9 @@
grid-row: 2; grid-row: 2;
grid-column: 2; grid-column: 2;
&.active { &.active {
overflow: hidden;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
margin: 0 0 10px 0;
} }
} }
} }

View file

@ -8,17 +8,14 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 10px; gap: 10px;
height: 100%; height: 100%;
overflow-y: auto; padding: 12px calc(12px - var(--scrollbar-width)) 4px 12px;
padding-top: 8px; .stable-scroll-container();
padding-bottom: 20px;
height: 100%;
.with-scroll-shadows();
} }
.characteristics-section { .characteristics-section {
gap: 20px; gap: 20px;
padding: 0 10px; padding: 0 4px;
} }
.biography-section { .biography-section {

View file

@ -8,9 +8,8 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 10px; gap: 10px;
overflow-y: auto; padding: 7px calc(12px - var(--scrollbar-width)) 4px 12px;
padding-bottom: 20px; .stable-scroll-container();
.with-scroll-shadows();
} }
} }
} }

View file

@ -8,9 +8,8 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 10px; gap: 10px;
overflow-y: auto; padding: 7px calc(12px - var(--scrollbar-width)) 4px 12px;
padding-bottom: 20px; .stable-scroll-container();
.with-scroll-shadows();
} }
} }
} }

View file

@ -19,16 +19,19 @@
.application.sheet.daggerheart.actor.dh-style.character { .application.sheet.daggerheart.actor.dh-style.character {
.character-header-sheet { .character-header-sheet {
padding: 0 15px;
padding-top: var(--header-height); padding-top: var(--header-height);
width: 100%; width: 100%;
> *:not(line-div, .tab-navigation) {
padding-left: 15px;
padding-right: 15px;
}
.name-row { .name-row {
display: flex; display: flex;
gap: 6px; gap: 6px;
align-items: start; align-items: start;
justify-content: space-between; justify-content: space-between;
padding: 0;
padding-top: 5px; padding-top: 5px;
flex: 1; flex: 1;
@ -100,8 +103,8 @@
.character-details { .character-details {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
padding: 5px 0; margin-top: 5px;
margin-bottom: 8px; margin-bottom: 10px;
font-size: var(--font-size-12); font-size: var(--font-size-12);
color: @color-text-emphatic; color: @color-text-emphatic;
@ -130,7 +133,6 @@
.character-row { .character-row {
display: flex; display: flex;
align-items: center; align-items: center;
padding: 0;
margin-bottom: 12px; margin-bottom: 12px;
.resource-section { .resource-section {
@ -218,12 +220,11 @@
.character-traits { .character-traits {
display: flex; display: flex;
padding: 0;
margin-bottom: 15px; margin-bottom: 15px;
justify-content: space-between; justify-content: space-between;
max-width: 38.5rem; max-width: 38.5rem;
gap: 0.5rem; gap: 0.5rem;
padding-left: 0.5rem; margin-left: 0.5rem;
.trait { .trait {
cursor: pointer; cursor: pointer;
@ -325,5 +326,9 @@
} }
} }
} }
.tab-navigation button[data-action="openSettings"] {
margin-right: 12px;
}
} }
} }

View file

@ -1,8 +1,8 @@
@import './sheet.less';
@import './biography.less'; @import './biography.less';
@import './effects.less'; @import './effects.less';
@import './features.less'; @import './features.less';
@import './header.less'; @import './header.less';
@import './inventory.less'; @import './inventory.less';
@import './loadout.less'; @import './loadout.less';
@import './sheet.less';
@import './sidebar.less'; @import './sidebar.less';

View file

@ -7,11 +7,9 @@
.items-section { .items-section {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 10px; gap: 10px;
overflow-y: auto; padding: 8px calc(12px - var(--scrollbar-width)) 4px 12px;
margin-top: 20px; .stable-scroll-container();
padding-bottom: 20px;
.with-scroll-shadows();
} }
} }
} }

View file

@ -50,11 +50,8 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 10px; gap: 10px;
height: 100%; padding: 8px calc(12px - var(--scrollbar-width)) 4px 12px;
overflow-y: auto; .stable-scroll-container();
margin-top: 20px;
padding-bottom: 20px;
.with-scroll-shadows();
} }
} }
} }

View file

@ -30,7 +30,7 @@
&.active { &.active {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow: hidden; margin: 0 0 10px 0;
} }
} }
} }

View file

@ -1,7 +1,10 @@
@import '../../../utils/colors.less'; @import '../../../utils/colors.less';
@import '../../../utils/fonts.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, .partner-section,
.attack-section, .attack-section,
.experience-list { .experience-list {

View file

@ -6,9 +6,8 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 10px; gap: 10px;
overflow-y: auto; padding: 7px calc(12px - var(--scrollbar-width)) 4px 12px;
padding-bottom: 20px; .stable-scroll-container();
.with-scroll-shadows();
} }
} }
} }

View file

@ -5,12 +5,8 @@
.application.sheet.daggerheart.actor.dh-style.environment { .application.sheet.daggerheart.actor.dh-style.environment {
.tab.features { .tab.features {
.feature-section { .feature-section {
display: flex; padding: 16px calc(16px - var(--scrollbar-width)) 4px 16px;
flex-direction: column; .stable-scroll-container();
gap: 10px;
overflow-y: auto;
padding-bottom: 4px;
.with-scroll-shadows();
} }
} }
} }

View file

@ -1,4 +1,4 @@
@import './sheet.less';
@import './features.less'; @import './features.less';
@import './header.less'; @import './header.less';
@import './potentialAdversaries.less'; @import './potentialAdversaries.less';
@import './sheet.less';

View file

@ -6,9 +6,8 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 10px; gap: 10px;
overflow-y: auto; padding: 7px calc(12px - var(--scrollbar-width)) 4px 12px;
padding-bottom: 4px; .stable-scroll-container();
.with-scroll-shadows();
} }
} }
} }

View file

@ -14,9 +14,7 @@
.application.sheet.daggerheart.actor.dh-style.environment { .application.sheet.daggerheart.actor.dh-style.environment {
.tab { .tab {
flex: 1;
overflow-y: auto; overflow-y: auto;
&.active { &.active {
overflow: hidden; overflow: hidden;
display: flex; display: flex;

View file

@ -7,12 +7,8 @@
} }
.feature-section { .feature-section {
display: flex; padding: 16px calc(16px - var(--scrollbar-width)) 4px 16px;
flex-direction: column; .stable-scroll-container();
gap: 10px;
overflow-y: auto;
padding-bottom: 4px;
.with-scroll-shadows();
} }
} }
} }

View file

@ -3,7 +3,8 @@
@import '../../../utils/mixin.less'; @import '../../../utils/mixin.less';
.application.sheet.daggerheart.actor.dh-style.party .tab.partyMembers { .application.sheet.daggerheart.actor.dh-style.party .tab.partyMembers {
overflow: auto; padding: 12px calc(12px - var(--scrollbar-width)) 4px 12px;
.stable-scroll-container();
.actions-section { .actions-section {
display: flex; display: flex;

View file

@ -17,15 +17,8 @@
}); });
.application.sheet.daggerheart.actor.dh-style.party { .application.sheet.daggerheart.actor.dh-style.party {
.tab { .tab.active {
flex: 1; display: flex;
overflow-y: auto; flex-direction: column;
scrollbar-gutter: stable;
&.active {
overflow: auto;
display: flex;
flex-direction: column;
}
} }
} }

View file

@ -174,10 +174,10 @@
--fade-start: 0; --fade-start: 0;
} }
10%, 100% { 10%, 100% {
--fade-start: 12px; --fade-start: 14px;
} }
0%, 90% { 0%, 90% {
--fade-end: 12px; --fade-end: 14px;
} }
100% { 100% {
--fade-end: 0; --fade-end: 0;
@ -198,3 +198,9 @@
transparent 100% transparent 100%
); );
} }
.stable-scroll-container() {
overflow-y: auto;
scrollbar-gutter: stable;
.with-scroll-shadows();
}

View file

@ -2,7 +2,7 @@
"id": "daggerheart", "id": "daggerheart",
"title": "Daggerheart", "title": "Daggerheart",
"description": "An unofficial implementation of the Daggerheart system", "description": "An unofficial implementation of the Daggerheart system",
"version": "2.4.2", "version": "2.5.0",
"compatibility": { "compatibility": {
"minimum": "14.364", "minimum": "14.364",
"verified": "14.364", "verified": "14.364",
@ -10,7 +10,7 @@
}, },
"url": "https://github.com/Foundryborne/daggerheart", "url": "https://github.com/Foundryborne/daggerheart",
"manifest": "https://raw.githubusercontent.com/Foundryborne/daggerheart/v14/system.json", "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": [ "authors": [
{ {
"name": "WBHarry" "name": "WBHarry"

View file

@ -1,14 +1,15 @@
<section class='tab {{tabs.features.cssClass}} {{tabs.features.id}}' data-tab='{{tabs.features.id}}' <section class='tab {{tabs.features.cssClass}} {{tabs.features.id}}' data-tab='{{tabs.features.id}}'
data-group='{{tabs.features.group}}'> data-group='{{tabs.features.group}}'>
<div class="feature-section"> <div class="feature-section items-list">
{{> 'daggerheart.inventory-items' {{#each @root.features as |item|}}
title=tabs.features.label {{> "daggerheart.inventory-item"
type='feature' item=item
collection=@root.features type="feature"
hideContextMenu=true actorType=@root.document.type
hideModifyControls=true hideContextMenu=true
canCreate=@root.editable hideModifyControls=true
showActions=@root.editable showActions=@root.editable
}} }}
{{/each}}
</div> </div>
</section> </section>

View file

@ -1,26 +1,26 @@
<section class='tab {{tabs.features.cssClass}} {{tabs.features.id}}' data-tab='{{tabs.features.id}}' <section class='tab {{tabs.features.cssClass}} {{tabs.features.id}}' data-tab='{{tabs.features.id}}'
data-group='{{tabs.features.group}}'> data-group='{{tabs.features.group}}'>
<div class="features-sections"> <div class="features-sections">
{{#each document.system.sheetLists as |category|}} {{#each document.system.sheetLists as |category|}}
{{#if (eq category.type 'feature' )}} {{#if (eq category.type 'feature' )}}
{{> 'daggerheart.inventory-items' {{> 'daggerheart.inventory-items'
title=category.title title=category.title
type='feature' type='feature'
actorType='character' actorType='character'
collection=category.values collection=category.values
canCreate=@root.editable canCreate=@root.editable
showActions=@root.editable showActions=@root.editable
}} }}
{{else if category.values}} {{else if category.values}}
{{> 'daggerheart.inventory-items' {{> 'daggerheart.inventory-items'
title=category.title title=category.title
type='feature' type='feature'
actorType='character' actorType='character'
collection=category.values collection=category.values
canCreate=false canCreate=false
showActions=@root.editable showActions=@root.editable
}} }}
{{/if}} {{/if}}
{{/each}} {{/each}}
</div> </div>
</section> </section>

View file

@ -1,165 +1,163 @@
<header class="character-header-sheet"> <header class="character-header-sheet">
<line-div></line-div> <line-div></line-div>
<div class="name-row"> <div class="name-row">
<h1 <h1
class="actor-name input" class="actor-name input"
contenteditable="plaintext-only" contenteditable="plaintext-only"
data-property="name" data-property="name"
placeholder="{{localize "DAGGERHEART.GENERAL.actorName"}}" placeholder="{{localize "DAGGERHEART.GENERAL.actorName"}}"
autocomplete="off" autocomplete="off"
spellcheck="false" spellcheck="false"
>{{source.name}}</h1> >{{source.name}}</h1>
<div class='level-div'> <div class='level-div'>
<h3 class='label'> <h3 class='label'>
{{#if @root.editable}} {{#if @root.editable}}
{{#if document.system.needsCharacterSetup}} {{#if document.system.needsCharacterSetup}}
<button <button
type="button" type="button"
class="level-button glow" class="level-button glow"
data-action="levelManagement" data-action="levelManagement"
> >
{{localize "DAGGERHEART.APPLICATIONS.CharacterCreation.buttonTitle"}} {{localize "DAGGERHEART.APPLICATIONS.CharacterCreation.buttonTitle"}}
</button> </button>
{{else if document.system.levelData.canLevelUp}} {{else if document.system.levelData.canLevelUp}}
<button <button
type="button" type="button"
class="level-button glow" data-tooltip="{{localize "DAGGERHEART.ACTORS.Character.levelUp"}}" class="level-button glow" data-tooltip="{{localize "DAGGERHEART.ACTORS.Character.levelUp"}}"
data-action="levelManagement" data-action="levelManagement"
> >
<i class="fa-solid fa-angles-up"></i> <i class="fa-solid fa-angles-up"></i>
</button> </button>
{{/if}} {{/if}}
{{/if}} {{/if}}
{{#unless document.system.needsCharacterSetup}} {{#unless document.system.needsCharacterSetup}}
{{localize 'DAGGERHEART.GENERAL.level'}} {{localize 'DAGGERHEART.GENERAL.level'}}
<input type="text" data-dtype="Number" class="level-value" value={{#if document.system.needsCharacterSetup}}0{{else}}{{document.system.levelData.level.changed}}{{/if}} {{#if document.system.needsCharacterSetup}}disabled{{/if}} /> <input type="text" data-dtype="Number" class="level-value" value={{#if document.system.needsCharacterSetup}}0{{else}}{{document.system.levelData.level.changed}}{{/if}} {{#if document.system.needsCharacterSetup}}disabled{{/if}} />
{{/unless}} {{/unless}}
</h3> </h3>
</div> </div>
</div> </div>
<div class="character-details"> <div class="character-details">
<div> <div>
{{#if document.system.class.value}} {{#if document.system.class.value}}
<span data-action="editDoc" data-item-uuid="{{document.system.class.value.uuid}}">{{document.system.class.value.name}}</span> <span data-action="editDoc" data-item-uuid="{{document.system.class.value.uuid}}">{{document.system.class.value.name}}</span>
{{else}} {{else}}
<span class="missing-header-feature" data-action="openPack" data-key="classes">{{localize 'TYPES.Item.class'}}</span> <span class="missing-header-feature" data-action="openPack" data-key="classes">{{localize 'TYPES.Item.class'}}</span>
{{/if}} {{/if}}
<span class="dot">•</span> <span class="dot">•</span>
{{#if document.system.class.subclass}} {{#if document.system.class.subclass}}
<span data-action="editDoc" data-item-uuid="{{document.system.class.subclass.uuid}}">{{document.system.class.subclass.name}}</span> <span data-action="editDoc" data-item-uuid="{{document.system.class.subclass.uuid}}">{{document.system.class.subclass.name}}</span>
{{else}} {{else}}
<span class="missing-header-feature" data-action="openPack" data-key="subclasses">{{localize 'TYPES.Item.subclass'}}</span> <span class="missing-header-feature" data-action="openPack" data-key="subclasses">{{localize 'TYPES.Item.subclass'}}</span>
{{/if}} {{/if}}
<span class="dot">•</span> <span class="dot">•</span>
{{#if document.system.community}} {{#if document.system.community}}
<span data-action="editDoc" data-item-uuid="{{document.system.community.uuid}}">{{document.system.community.name}}</span> <span data-action="editDoc" data-item-uuid="{{document.system.community.uuid}}">{{document.system.community.name}}</span>
{{else}} {{else}}
<span class="missing-header-feature" data-action="openPack" data-key="communities">{{localize 'TYPES.Item.community'}}</span> <span class="missing-header-feature" data-action="openPack" data-key="communities">{{localize 'TYPES.Item.community'}}</span>
{{/if}} {{/if}}
<span class="dot">•</span> <span class="dot">•</span>
{{#if document.system.ancestry}} {{#if document.system.ancestry}}
<span data-action="editDoc" data-item-uuid="{{document.system.ancestry.uuid}}">{{document.system.ancestry.name}}</span> <span data-action="editDoc" data-item-uuid="{{document.system.ancestry.uuid}}">{{document.system.ancestry.name}}</span>
{{else}} {{else}}
<span class="missing-header-feature" data-action="openPack" data-key="ancestries">{{localize 'TYPES.Item.ancestry'}}</span> <span class="missing-header-feature" data-action="openPack" data-key="ancestries">{{localize 'TYPES.Item.ancestry'}}</span>
{{/if}} {{/if}}
</div> </div>
{{#if (or document.system.multiclass.value document.system.multiclass.subclass)}} {{#if (or document.system.multiclass.value document.system.multiclass.subclass)}}
<div class="multiclass"> <div class="multiclass">
{{#if document.system.multiclass.value}} {{#if document.system.multiclass.value}}
<span data-action="editDoc"data-item-uuid="{{document.system.multiclass.value.uuid}}">{{document.system.multiclass.value.name}}</span> <span data-action="editDoc"data-item-uuid="{{document.system.multiclass.value.uuid}}">{{document.system.multiclass.value.name}}</span>
{{else}} {{else}}
<span data-action="openPack" data-key="classes">{{localize 'DAGGERHEART.GENERAL.multiclass'}}</span> <span data-action="openPack" data-key="classes">{{localize 'DAGGERHEART.GENERAL.multiclass'}}</span>
{{/if}} {{/if}}
<span class="dot">•</span> <span class="dot">•</span>
{{#if document.system.multiclass.subclass}} {{#if document.system.multiclass.subclass}}
<span data-action="editDoc" data-item-uuid="{{document.system.multiclass.subclass.uuid}}">{{document.system.multiclass.subclass.name}}</span> <span data-action="editDoc" data-item-uuid="{{document.system.multiclass.subclass.uuid}}">{{document.system.multiclass.subclass.name}}</span>
{{else}} {{else}}
<span class="missing-header-feature" data-action="openPack" data-key="subclasses">{{localize 'TYPES.Item.subclass'}}</span> <span class="missing-header-feature" data-action="openPack" data-key="subclasses">{{localize 'TYPES.Item.subclass'}}</span>
{{/if}} {{/if}}
</div> </div>
{{/if}} {{/if}}
</div>
</div> <div class="character-row">
<div class="resource-section">
<div class="character-row"> <div class="hope-section">
<div class="resource-section"> <h4>{{localize "DAGGERHEART.GENERAL.hope"}}</h4>
<div class="hope-section"> {{#times document.system.resources.hope.max}}
<h4>{{localize "DAGGERHEART.GENERAL.hope"}}</h4> <span class='hope-value' data-action='toggleHope' data-value="{{add this 1}}">
{{#times document.system.resources.hope.max}} {{#if (gte ../document.system.resources.hope.value (add this 1))}}
<span class='hope-value' data-action='toggleHope' data-value="{{add this 1}}"> <i class='fa-solid fa-diamond'></i>
{{#if (gte ../document.system.resources.hope.value (add this 1))}} {{else}}
<i class='fa-solid fa-diamond'></i> <i class='fa-regular fa-circle'></i>
{{else}} {{/if}}
<i class='fa-regular fa-circle'></i> </span>
{{/if}} {{/times}}
</span> {{#times document.system.scars}}
{{/times}} <span class='hope-value scar'>
{{#times document.system.scars}} <i class='fa-regular fa-ban'></i>
<span class='hope-value scar'> </span>
<i class='fa-regular fa-ban'></i> {{/times}}
</span> {{#if hasExtraResources}}<a type="button" class="resource-manager" data-action="toggleResourceManagement"><i class="fa-solid fa-angle-down"></i></a>{{/if}}
{{/times}} </div>
{{#if hasExtraResources}}<a type="button" class="resource-manager" data-action="toggleResourceManagement"><i class="fa-solid fa-angle-down"></i></a>{{/if}} </div>
</div> {{#if document.system.class.value}}
</div> <div class="domains-section">
{{#if document.system.class.value}} {{#each document.system.domainData as |data|}}
<div class="domains-section"> <div class="domain">
{{#each document.system.domainData as |data|}} <img src="{{data.src}}" alt="" data-tooltip="{{data.label}}" />
<div class="domain"> </div>
<img src="{{data.src}}" alt="" data-tooltip="{{data.label}}" /> {{/each}}
</div> </div>
{{/each}} {{/if}}
</div> <div class="downtime-section">
{{/if}} {{#if document.parties.size}}
<div class="downtime-section"> <button type="button" data-action="viewParty" data-tooltip="DAGGERHEART.ACTORS.Character.viewParty">
{{#if document.parties.size}} <i class="fa-solid fa-fw fa-users"></i>
<button type="button" data-action="viewParty" data-tooltip="DAGGERHEART.ACTORS.Character.viewParty"> </button>
<i class="fa-solid fa-fw fa-users"></i> {{/if}}
</button> {{#if @root.editable}}
{{/if}} <button type="button" data-action="useDowntime" data-type="shortRest" data-tooltip="DAGGERHEART.APPLICATIONS.Downtime.shortRest.title">
{{#if @root.editable}} <i class="fa-solid fa-fw fa-utensils"></i>
<button type="button" data-action="useDowntime" data-type="shortRest" data-tooltip="DAGGERHEART.APPLICATIONS.Downtime.shortRest.title"> </button>
<i class="fa-solid fa-fw fa-utensils"></i> <button type="button" data-action="useDowntime" data-type="longRest" data-tooltip="DAGGERHEART.APPLICATIONS.Downtime.longRest.title">
</button> <i class="fa-solid fa-fw fa-bed"></i>
<button type="button" data-action="useDowntime" data-type="longRest" data-tooltip="DAGGERHEART.APPLICATIONS.Downtime.longRest.title"> </button>
<i class="fa-solid fa-fw fa-bed"></i> {{/if}}
</button> </div>
{{/if}} </div>
</div>
</div> <div class="character-traits">
{{#each this.attributes as |attribute key|}}
<div class="character-traits"> <div class="trait {{#if isSpellcasting}}with-spellcasting{{/if}}" data-tooltip="<b>{{attribute.label}}:</b><br>{{#each attribute.verbs}}{{this}}<br>{{/each}}" data-action="rollAttribute" data-attribute="{{key}}" data-value="{{attribute.value}}">
{{#each this.attributes as |attribute key|}} <div class="trait-name">
<div class="trait {{#if isSpellcasting}}with-spellcasting{{/if}}" data-tooltip="<b>{{attribute.label}}:</b><br>{{#each attribute.verbs}}{{this}}<br>{{/each}}" data-action="rollAttribute" data-attribute="{{key}}" data-value="{{attribute.value}}"> <div class="tier-mark {{#if attribute.tierMarked}}marked{{/if}}"></div>
<div class="trait-name"> <span>{{attribute.label}}</span>
<div class="tier-mark {{#if attribute.tierMarked}}marked{{/if}}"></div> </div>
<span>{{attribute.label}}</span> <div class="trait-value-area">
</div> <svg width="52" height="46" viewBox="0 0 52 46" fill="none" xmlns="http://www.w3.org/2000/svg">
<div class="trait-value-area"> <path d="M 0,0 H 52 L 51.0714,18.254 48.781,24.0952 47.5745,39.9596 26,46 4.42553,39.9596 3.219,24.0952 0.928571,18.254 Z" fill="var(--color-border)"/>
<svg width="52" height="46" viewBox="0 0 52 46" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M 3.312,5.357 8.58596,0 H 43.414 l 5.274,5.357 -1.8797,28.846 c -0.2431,2.605 -2.1461,4.7522 -4.7031,5.3064 l -14.8343,3.2151 c -0.8375,0.1816 -1.7043,0.1816 -2.5418,0 L 9.89482,39.5094 C 7.33785,38.9552 5.43478,36.808 5.19169,34.203 Z" fill="var(--background)"/>
<path d="M 0,0 H 52 L 51.0714,18.254 48.781,24.0952 47.5745,39.9596 26,46 4.42553,39.9596 3.219,24.0952 0.928571,18.254 Z" fill="var(--color-border)"/> </svg>
<path d="M 3.312,5.357 8.58596,0 H 43.414 l 5.274,5.357 -1.8797,28.846 c -0.2431,2.605 -2.1461,4.7522 -4.7031,5.3064 l -14.8343,3.2151 c -0.8375,0.1816 -1.7043,0.1816 -2.5418,0 L 9.89482,39.5094 C 7.33785,38.9552 5.43478,36.808 5.19169,34.203 Z" fill="var(--background)"/> <div class="trait-value">
</svg> {{#if (gt attribute.value 0)}}
<div class="trait-value"> <span>+{{attribute.value}}</span>
{{#if (gt attribute.value 0)}} {{else}}
<span>+{{attribute.value}}</span> <span>{{attribute.value}}</span>
{{else}} {{/if}}
<span>{{attribute.value}}</span> </div>
{{/if}} {{#if isSpellcasting}}
</div> <div class="spellcasting-mark" data-tooltip="DAGGERHEART.ITEMS.Subclass.spellcastingTrait">
{{#if isSpellcasting}} <i class="fa-solid fa-wand-magic-sparkles" inert></i>
<div class="spellcasting-mark" data-tooltip="DAGGERHEART.ITEMS.Subclass.spellcastingTrait"> </div>
<i class="fa-solid fa-wand-magic-sparkles" inert></i> {{/if}}
</div> </div>
{{/if}} </div>
</div> {{/each}}
</div> </div>
{{/each}}
</div> {{#> 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' }}
<button type="button" data-action="openSettings" data-tooltip-text="{{localize "DAGGERHEART.UI.Tooltip.openSheetSettings"}}"><i class="fa-solid fa-wrench"></i></button>
{{#> 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' }} {{/'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs'}}
<button type="button" data-action="openSettings" data-tooltip-text="{{localize "DAGGERHEART.UI.Tooltip.openSheetSettings"}}"><i class="fa-solid fa-wrench"></i></button>
{{/'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs'}}
</header> </header>

View file

@ -23,7 +23,6 @@
collection=@root.inventory.weapons collection=@root.inventory.weapons
isGlassy=true isGlassy=true
canCreate=@root.editable canCreate=@root.editable
hideResources=true
}} }}
{{> 'daggerheart.inventory-items' {{> 'daggerheart.inventory-items'
title='TYPES.Item.armor' title='TYPES.Item.armor'

View file

@ -3,15 +3,16 @@
data-tab='{{tabs.features.id}}' data-tab='{{tabs.features.id}}'
data-group='{{tabs.features.group}}' data-group='{{tabs.features.group}}'
> >
<div class="feature-section"> <div class="feature-section items-list">
{{> 'daggerheart.inventory-items' {{#each @root.features as |item|}}
title=tabs.features.label {{> "daggerheart.inventory-item"
type='feature' item=item
collection=@root.features type="feature"
hideContextMenu=true actorType=@root.document.type
hideModifyControls=true hideContextMenu=true
canCreate=@root.editable hideModifyControls=true
showActions=@root.editable showActions=@root.editable
}} }}
{{/each}}
</div> </div>
</section> </section>

View file

@ -1,14 +1,15 @@
<section class='tab {{tabs.features.cssClass}} {{tabs.features.id}}' data-tab='{{tabs.features.id}}' <section class='tab {{tabs.features.cssClass}} {{tabs.features.id}}' data-tab='{{tabs.features.id}}'
data-group='{{tabs.features.group}}'> data-group='{{tabs.features.group}}'>
<div class="feature-section"> <div class="feature-section items-list">
{{> 'daggerheart.inventory-items' {{#each @root.features as |item|}}
title=tabs.features.label {{> "daggerheart.inventory-item"
type='feature' item=item
collection=@root.features type="feature"
hideContextMenu=true actorType=@root.document.type
hideModifyControls=true hideContextMenu=true
canCreate=@root.editable hideModifyControls=true
showActions=@root.editable showActions=@root.editable
}} }}
{{/each}}
</div> </div>
</section> </section>

View file

@ -45,4 +45,5 @@
<span>{{localize "TYPES.Item.feature"}}</span> <span>{{localize "TYPES.Item.feature"}}</span>
<input type="text" class="features-input" value="{{features}}" /> <input type="text" class="features-input" value="{{features}}" />
</fieldset> </fieldset>
{{> "systems/daggerheart/templates/sheets/global/partials/resource-section/resource-section.hbs" }}
</section> </section>