Merged with main

This commit is contained in:
WBHarry 2026-07-06 23:16:00 +02:00
commit 2780ba3f40
86 changed files with 1957 additions and 1984 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"
} }
}, },
@ -113,7 +114,8 @@
"area": { "area": {
"sectionTitle": "Areas", "sectionTitle": "Areas",
"shape": "Shape", "shape": "Shape",
"size": "Size" "size": "Size",
"hasHole": "Area Hole"
}, },
"displayInChat": "Display in chat", "displayInChat": "Display in chat",
"deleteTriggerTitle": "Delete Trigger", "deleteTriggerTitle": "Delete Trigger",
@ -868,6 +870,10 @@
"bruiser": "for each Bruiser adversary.", "bruiser": "for each Bruiser adversary.",
"solo": "for each Solo adversary." "solo": "for each Solo adversary."
}, },
"AreaTypes": {
"attached": { "label": "Attached To Token" },
"placed": { "label": "Placed In Scene" }
},
"ArmorInteraction": { "ArmorInteraction": {
"none": { "label": "Ignores Armor" }, "none": { "label": "Ignores Armor" },
"active": { "label": "Active w/ Armor" }, "active": { "label": "Active w/ Armor" },

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: 660, height: 766 }, position: { width: 645, height: 750 },
window: { resizable: true }, window: { resizable: true },
actions: { actions: {
toggleHitPoints: AdversarySheet.#toggleHitPoints, toggleHitPoints: AdversarySheet.#toggleHitPoints,
@ -58,12 +58,13 @@ export default class AdversarySheet extends DHBaseActorSheet {
template: 'systems/daggerheart/templates/sheets/actors/adversary/features.hbs', template: 'systems/daggerheart/templates/sheets/actors/adversary/features.hbs',
scrollable: ['.feature-section'] scrollable: ['.feature-section']
}, },
notes: {
template: 'systems/daggerheart/templates/sheets/actors/adversary/notes.hbs'
},
effects: { effects: {
template: 'systems/daggerheart/templates/sheets/actors/adversary/effects.hbs', template: 'systems/daggerheart/templates/sheets/actors/adversary/effects.hbs',
scrollable: ['.effects-sections'] scrollable: ['.effects-sections']
},
notes: {
template: 'systems/daggerheart/templates/sheets/actors/adversary/notes.hbs',
scrollable: ['.editor-content']
} }
}; };

View file

@ -1057,7 +1057,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
direction: 'DOWN' direction: 'DOWN'
}); });
html.querySelectorAll('.armor-slot').forEach(element => { html.querySelectorAll('.armor .slot').forEach(element => {
element.addEventListener('click', CharacterSheet.armorSourcePipUpdate); element.addEventListener('click', CharacterSheet.armorSourcePipUpdate);
}); });
} }
@ -1072,7 +1072,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
/** Update specific armor source */ /** Update specific armor source */
static async armorSourcePipUpdate(event) { static async armorSourcePipUpdate(event) {
const target = event.target.closest('.armor-slot'); const target = event.target.closest('.slot');
const { uuid, value } = target.dataset; const { uuid, value } = target.dataset;
const document = await foundry.utils.fromUuid(uuid); const document = await foundry.utils.fromUuid(uuid);
@ -1100,7 +1100,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
} }
const container = target.closest('.slot-bar'); 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); const index = Number.parseInt(armorSlot.dataset.index);
if (decreasing && index >= newCurrent) { if (decreasing && index >= newCurrent) {
armorSlot.classList.remove('fa-shield'); armorSlot.classList.remove('fa-shield');

View file

@ -305,7 +305,7 @@ export default function DHApplicationMixin(Base) {
_preSyncPartState(partId, newElement, priorElement, state) { _preSyncPartState(partId, newElement, priorElement, state) {
super._preSyncPartState(partId, newElement, priorElement, state); super._preSyncPartState(partId, newElement, priorElement, state);
for (const el of priorElement.querySelectorAll('.extensible.extended')) { 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 selector = `${actionId ? `[data-action-id="${actionId}"]` : `[data-item-uuid="${itemUuid}"]`} .extensible`;
const newExtensible = newElement.querySelector(selector); const newExtensible = newElement.querySelector(selector);
newExtensible?.classList.add('extended'); newExtensible?.classList.add('extended');
@ -603,7 +603,7 @@ export default function DHApplicationMixin(Base) {
const doc = await fromUuid(itemUuid); const doc = await fromUuid(itemUuid);
//get inventory-item description element //get inventory-item description element
const descriptionElement = el.querySelector('.invetory-description'); const descriptionElement = el.querySelector('.inventory-description');
if (!doc || !descriptionElement) continue; if (!doc || !descriptionElement) continue;
// localize the description (idk if it's still necessary) // localize the description (idk if it's still necessary)

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

@ -96,7 +96,7 @@ export default class DhRegionLayer extends foundry.canvas.layers.RegionLayer {
return inBounds.length === 1 ? inBounds[0] : null; 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; const { line, rectangle, inFront, cone, circle, emanation } = CONFIG.DH.GENERAL.templateTypes;
/* Length calculation */ /* Length calculation */
@ -112,11 +112,11 @@ export default class DhRegionLayer extends foundry.canvas.layers.RegionLayer {
const shapeData = { const shapeData = {
...canvas.mousePosition, ...canvas.mousePosition,
type: type, type: shapeType,
direction: direction ?? 0 direction: direction ?? 0
}; };
switch (type) { switch (shapeType) {
case rectangle.id: case rectangle.id:
shapeData.width = length; shapeData.width = length;
shapeData.height = length; shapeData.height = length;
@ -145,7 +145,8 @@ export default class DhRegionLayer extends foundry.canvas.layers.RegionLayer {
y: 0, y: 0,
width: 1, width: 1,
height: 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; break;
} }

View file

@ -119,6 +119,10 @@ export const advantageState = {
export const areaTypes = { export const areaTypes = {
placed: { placed: {
id: 'placed', id: 'placed',
label: 'Placed Area' label: 'DAGGERHEART.CONFIG.AreaTypes.placed.label'
},
attached: {
id: 'attached',
label: 'DAGGERHEART.CONFIG.AreaTypes.attached.label'
} }
}; };

View file

@ -54,6 +54,10 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
return {}; return {};
} }
get hasDescription() {
return Boolean(this.description);
}
/** /**
* Create a Map containing each Action step based on fields define in schema. Ordered by Fields order property. * Create a Map containing each Action step based on fields define in schema. Ordered by Fields order property.
* *
@ -110,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;
} }
@ -139,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
@ -151,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;
@ -447,7 +456,15 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
static migrateData(source) { static migrateData(source) {
if (source.damage?.parts && Array.isArray(source.damage.parts)) { if (source.damage?.parts && Array.isArray(source.damage.parts)) {
let hitPointsExists = source.damage.parts.some(x => x.applyTo === 'hitPoints');
source.damage.parts = source.damage.parts.reduce((acc, part) => { source.damage.parts = source.damage.parts.reduce((acc, part) => {
if (!part.applyTo && hitPointsExists) return acc;
if (!part.applyTo) {
hitPointsExists = true;
part.applyTo = 'hitPoints';
}
acc[part.applyTo] = part; acc[part.applyTo] = part;
return acc; return acc;
}, {}); }, {});

View file

@ -87,6 +87,7 @@ export default class DhpAdversary extends DhCreature {
parts: { parts: {
hitPoints: { hitPoints: {
type: ['physical'], type: ['physical'],
applyTo: 'hitPoints',
value: { value: {
multiplier: 'flat' multiplier: 'flat'
} }

View file

@ -107,6 +107,7 @@ export default class DhCharacter extends DhCreature {
parts: { parts: {
hitPoints: { hitPoints: {
type: ['physical'], type: ['physical'],
applyTo: 'hitPoints',
value: { value: {
custom: { custom: {
enabled: true, enabled: true,

View file

@ -102,6 +102,7 @@ export default class DhCompanion extends DhCreature {
parts: { parts: {
hitPoints: { hitPoints: {
type: ['physical'], type: ['physical'],
applyTo: 'hitPoints',
value: { value: {
dice: 'd6', dice: 'd6',
multiplier: 'prof' multiplier: 'prof'

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

@ -28,6 +28,39 @@ export default class DhCountdowns extends foundry.abstract.DataModel {
for (const countdownKey of changedCountdowns) for (const countdownKey of changedCountdowns)
foundry.ui.countdowns.changedCountdownsForAnimation.add(countdownKey); foundry.ui.countdowns.changedCountdownsForAnimation.add(countdownKey);
} }
static migrateData(source) {
const migrateOldCountdowns = (data, type) => {
for (const key of Object.keys(data.countdowns)) {
const countdown = data.countdowns[key];
source.countdowns[key] = {
...countdown,
type: type,
ownership: Object.keys(countdown.ownership.players).reduce((acc, key) => {
acc[key] =
countdown.ownership.players[key].type === 1 ? 2 : countdown.ownership.players[key].type;
return acc;
}, {}),
progress: {
...countdown.progress,
type: countdown.progress.type.value
}
};
}
source[type] = null;
};
if (source.narrative) {
migrateOldCountdowns(source.narrative, 'narrative');
}
if (source.encounter) {
migrateOldCountdowns(source.encounter, 'encounter');
}
return super.migrateData(source);
}
} }
export class DhCountdown extends foundry.abstract.DataModel { export class DhCountdown extends foundry.abstract.DataModel {

View file

@ -33,6 +33,10 @@ export default class AreasField extends fields.ArrayField {
initial: CONFIG.DH.GENERAL.range.veryClose.id, initial: CONFIG.DH.GENERAL.range.veryClose.id,
label: 'DAGGERHEART.ACTIONS.Config.area.size' 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()) effects: new fields.ArrayField(new fields.DocumentIdField())
}); });
super(element, options, context); super(element, options, context);

View file

@ -1,4 +1,4 @@
import { itemAbleRollParse, triggerChatRollFx } from '../../../helpers/utils.mjs'; import { getWorldActor, itemAbleRollParse, triggerChatRollFx } from '../../../helpers/utils.mjs';
import FormulaField from '../formulaField.mjs'; import FormulaField from '../formulaField.mjs';
const fields = foundry.data.fields; const fields = foundry.data.fields;
@ -42,7 +42,7 @@ export default class DHSummonField extends fields.ArrayField {
const count = roll.total; const count = roll.total;
if (!roll.isDeterministic) rolls.push(roll); if (!roll.isDeterministic) rolls.push(roll);
const actor = await DHSummonField.getWorldActor(await foundry.utils.fromUuid(summon.actorUUID)); const actor = await getWorldActor(await foundry.utils.fromUuid(summon.actorUUID));
/* Extending summon data in memory so it's available in actionField.toChat. Think it's harmless, but ugly. Could maybe find a better way. */ /* Extending summon data in memory so it's available in actionField.toChat. Think it's harmless, but ugly. Could maybe find a better way. */
summon.actor = actor.toObject(); summon.actor = actor.toObject();
@ -62,19 +62,6 @@ export default class DHSummonField extends fields.ArrayField {
DHSummonField.handleSummon(summonData, this.actor); DHSummonField.handleSummon(summonData, this.actor);
} }
/* Check for any available instances of the actor present in the world if we're missing artwork in the compendium. If none exists, create one. */
static async getWorldActor(baseActor) {
const dataType = game.system.api.data.actors[`Dh${baseActor.type.capitalize()}`];
if (baseActor.inCompendium && dataType && baseActor.img === dataType.DEFAULT_ICON) {
const worldActorCopy = game.actors.find(x => x.name === baseActor.name);
if (worldActorCopy) return worldActorCopy;
return await game.system.api.documents.DhpActor.create(baseActor.toObject());
}
return baseActor;
}
static async handleSummon(summonData, actionActor) { static async handleSummon(summonData, actionActor) {
await CONFIG.ux.TokenManager.createTokensWithPreview(summonData, { elevation: actionActor.token?.elevation }); await CONFIG.ux.TokenManager.createTokensWithPreview(summonData, { elevation: actionActor.token?.elevation });

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

@ -8,7 +8,8 @@ export default class DHArmor extends AttachableItem {
type: 'armor', type: 'armor',
hasDescription: true, hasDescription: true,
isInventoryItem: true, isInventoryItem: true,
hasActions: true hasActions: true,
hasResource: true
}); });
} }
@ -52,6 +53,10 @@ export default class DHArmor extends AttachableItem {
); );
} }
get itemFeatures() {
return this.armorFeatures;
}
/**@inheritdoc */ /**@inheritdoc */
async getDescriptionData() { async getDescriptionData() {
const baseDescription = this.description; const baseDescription = this.description;
@ -169,8 +174,4 @@ export default class DHArmor extends AttachableItem {
const labels = [`${game.i18n.localize('DAGGERHEART.ITEMS.Armor.baseScore')}: ${this.armor.max}`]; const labels = [`${game.i18n.localize('DAGGERHEART.ITEMS.Armor.baseScore')}: ${this.armor.max}`];
return labels; return labels;
} }
get itemFeatures() {
return this.armorFeatures;
}
} }

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
}); });
} }
@ -113,6 +114,10 @@ export default class DHWeapon extends AttachableItem {
); );
} }
get itemFeatures() {
return this.weaponFeatures;
}
/**@inheritdoc */ /**@inheritdoc */
async getDescriptionData() { async getDescriptionData() {
const baseDescription = this.description; const baseDescription = this.description;
@ -269,8 +274,4 @@ export default class DHWeapon extends AttachableItem {
return labels; return labels;
} }
get itemFeatures() {
return this.weaponFeatures;
}
} }

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

@ -65,6 +65,10 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect {
); );
} }
get hasDescription() {
return Boolean(this.description);
}
/* -------------------------------------------- */ /* -------------------------------------------- */
/* Event Handlers */ /* Event Handlers */
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -225,7 +229,7 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect {
*/ */
_getTags() { _getTags() {
const tags = []; const tags = [];
const originActor = DhActiveEffect.#resolveParentDocument(fromUuidSync(this.origin), Actor); const originActor = DhActiveEffect.#resolveParentDocument(fromUuidSync(this.origin, { strict: false }), Actor);
if (originActor && originActor !== this.actor) { if (originActor && originActor !== this.actor) {
tags.push(_loc('DAGGERHEART.EFFECTS.OriginTag', { name: originActor.name })); tags.push(_loc('DAGGERHEART.EFFECTS.OriginTag', { name: originActor.name }));
} else if (!(this.parent instanceof Actor)) { } else if (!(this.parent instanceof Actor)) {

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);
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -620,22 +622,30 @@ export default class DhpActor extends Actor {
return rollData; return rollData;
} }
#canReduceDamage(hpDamage, type) { #canReduceDamage(hpDamage, types) {
const { stressDamageReduction, disabledArmor } = this.system.rules.damageReduction; const { stressDamageReduction, disabledArmor, reduceSeverity, thresholdImmunities } =
this.system.rules.damageReduction;
if (disabledArmor) return false; if (disabledArmor) return false;
const availableStress = this.system.resources.stress.max - this.system.resources.stress.value; const availableStress = this.system.resources.stress.max - this.system.resources.stress.value;
const canUseArmor = const canUseArmor =
this.system.armorScore.value < this.system.armorScore.max && this.system.armorScore.value < this.system.armorScore.max &&
type.every(t => this.system.armorApplicableDamageTypes[t] === true); types.every(t => this.system.armorApplicableDamageTypes[t] === true);
const canUseStress = Object.keys(stressDamageReduction).reduce((acc, x) => { const canUseStress = Object.keys(stressDamageReduction).reduce((acc, x) => {
const rule = stressDamageReduction[x]; const rule = stressDamageReduction[x];
if (damageKeyToNumber(x) <= hpDamage) return acc || (rule.enabled && availableStress >= rule.cost); if (damageKeyToNumber(x) <= hpDamage) return acc || (rule.enabled && availableStress >= rule.cost);
return acc; return acc;
}, false); }, false);
return canUseArmor || canUseStress; const hasReduceSeverity = types.some(t => reduceSeverity[t]);
const hasThresholdImmunity = Object.entries(thresholdImmunities)
.filter(([key, value]) => Boolean(value) && damageKeyToNumber(key) === hpDamage)
.length;
return canUseArmor || canUseStress || hasReduceSeverity || hasThresholdImmunity;
} }
async takeDamage(damages, isDirect = false) { async takeDamage(damages, isDirect = false) {

View file

@ -276,8 +276,12 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
async onCreateAreas(event) { async onCreateAreas(event) {
const createArea = async selectedArea => { const createArea = async selectedArea => {
const effects = selectedArea.effects.map(effect => this.system.action.item.effects.get(effect).uuid); const effects = selectedArea.effects.map(effect => this.system.action.item.effects.get(effect).uuid);
const { shape: type, size: range } = selectedArea; const { shape: shapeType, size: range, hasHole } = selectedArea;
const shapeData = CONFIG.Canvas.layers.regions.layerClass.getTemplateShape({ type, range }); const shapeData = CONFIG.Canvas.layers.regions.layerClass.getTemplateShape({
shapeType,
range,
hasHole
});
const scene = game.scenes.get(game.user.viewedScene); const scene = game.scenes.get(game.user.viewedScene);
const level = scene.levels.find(x => x.isView); 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 visibility: CONST.REGION_VISIBILITY.ALWAYS
}; };
const placeRegion = data => { 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 // Regions with effects must be placed by the GM

View file

@ -46,7 +46,9 @@ export default class DhpCombat extends Combat {
for (let actor of actors) { for (let actor of actors) {
await actor.createEmbeddedDocuments( await actor.createEmbeddedDocuments(
'ActiveEffect', 'ActiveEffect',
effects.filter(x => x.effectTargetTypes.includes(actor.type)) effects
.filter(x => x.effectTargetTypes.includes(actor.type))
.map(x => foundry.utils.deepClone(x))
); );
} }
} else { } else {

View file

@ -89,6 +89,10 @@ export default class DHItem extends foundry.documents.Item {
return !pack?.locked && this.isOwner && isValidType && hasActions; return !pack?.locked && this.isOwner && isValidType && hasActions;
} }
get hasDescription() {
return Boolean(this.system.description) || Boolean(this.system.itemFeatures?.length);
}
/** @inheritdoc */ /** @inheritdoc */
static async createDialog(data = {}, createOptions = {}, options = {}) { static async createDialog(data = {}, createOptions = {}, options = {}) {
const { folders, types, template, context = {}, ...dialogOptions } = options; const { folders, types, template, context = {}, ...dialogOptions } = options;

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

@ -58,10 +58,11 @@ export const renderMeasuredTemplate = async event => {
if (!type || !range || !game.canvas.scene) return; if (!type || !range || !game.canvas.scene) return;
const shapeData = CONFIG.Canvas.layers.regions.layerClass.getTemplateShape({ const shapeData = CONFIG.Canvas.layers.regions.layerClass.getTemplateShape({
type, shapetype: type,
angle, angle,
range, range,
direction direction,
hasHole: false
}); });
await canvas.regions.placeRegion( await canvas.regions.placeRegion(

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);
}; };
@ -890,3 +894,32 @@ export function shouldUseHopeFearAutomation(options = { gmAsPlayer: true }) {
const { hopeFear } = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation); const { hopeFear } = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation);
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) {
if (baseActor.inCompendium) {
const worldActorCandidates = game.actors.filter(x =>
x._stats.compendiumSource === baseActor.uuid &&
x.prototypeToken.actorLink === baseActor.prototypeToken.actorLink
);
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({
...baseActorData,
_stats: {
...baseActorData._stats,
compendiumSource: baseActor.uuid
}
});
}
return baseActor;
}

View file

@ -1,5 +1,4 @@
import { defaultRestOptions } from '../config/generalConfig.mjs'; import { defaultRestOptions } from '../config/generalConfig.mjs';
import { RefreshType, socketEvent } from './socket.mjs';
export async function runMigrations() { export async function runMigrations() {
let lastMigrationVersion = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LastMigrationVersion); let lastMigrationVersion = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LastMigrationVersion);
@ -153,61 +152,26 @@ export async function runMigrations() {
await pack.configure({ locked: true }); await pack.configure({ locked: true });
} }
/* Migrate old countdown structure */
const countdownSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
const getCountdowns = (data, type) => {
return Object.keys(data.countdowns).reduce((acc, key) => {
const countdown = data.countdowns[key];
acc[key] = {
...countdown,
type: type,
ownership: Object.keys(countdown.ownership.players).reduce((acc, key) => {
acc[key] =
countdown.ownership.players[key].type === 1 ? 2 : countdown.ownership.players[key].type;
return acc;
}, {}),
progress: {
...countdown.progress,
type: countdown.progress.type.value
}
};
return acc;
}, {});
};
await countdownSettings.updateSource({
countdowns: {
...getCountdowns(countdownSettings.narrative, 'narrative'),
...getCountdowns(countdownSettings.encounter, 'encounter')
}
});
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, countdownSettings);
game.socket.emit(`system.${CONFIG.DH.id}`, {
action: socketEvent.Refresh,
data: { refreshType: RefreshType.Countdown }
});
Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.Countdown });
lastMigrationVersion = '1.2.0'; lastMigrationVersion = '1.2.0';
} }
if (foundry.utils.isNewerVersion('1.2.7', lastMigrationVersion)) { if (foundry.utils.isNewerVersion('1.2.7', lastMigrationVersion)) {
const tagTeam = game.settings.get(CONFIG.DH.id, 'TagTeamRoll'); try {
const initatorMissing = tagTeam.initiator && !game.actors.some(actor => actor.id === tagTeam.initiator); const tagTeam = game.settings.get(CONFIG.DH.id, 'TagTeamRoll');
const missingMembers = Object.keys(tagTeam.members).reduce((acc, id) => { const initatorMissing = tagTeam.initiator && !game.actors.some(actor => actor.id === tagTeam.initiator);
if (!game.actors.some(actor => actor.id === id)) { const missingMembers = Object.keys(tagTeam.members).reduce((acc, id) => {
acc[id] = _del; if (!game.actors.some(actor => actor.id === id)) {
} acc[id] = _del;
return acc; }
}, {}); return acc;
}, {});
await tagTeam.updateSource({ await tagTeam.updateSource({
initiator: initatorMissing ? null : tagTeam.initiator, initiator: initatorMissing ? null : tagTeam.initiator,
members: missingMembers members: missingMembers
}); });
await game.settings.set(CONFIG.DH.id, 'TagTeamRoll', tagTeam); await game.settings.set(CONFIG.DH.id, 'TagTeamRoll', tagTeam);
} catch { }
lastMigrationVersion = '1.2.7'; lastMigrationVersion = '1.2.7';
} }
@ -303,6 +267,8 @@ export async function runMigrations() {
/* Migrate existing effects modifying armor, creating new Armor Effects instead */ /* Migrate existing effects modifying armor, creating new Armor Effects instead */
const migrateEffects = async entity => { const migrateEffects = async entity => {
if (!entity?.effects) return;
for (const effect of entity.effects) { for (const effect of entity.effects) {
if (effect.system.changes.every(x => x.key !== 'system.armorScore')) continue; if (effect.system.changes.every(x => x.key !== 'system.armorScore')) continue;

View file

@ -10,7 +10,7 @@
"type": "damage", "type": "damage",
"_id": "Cc53vyYz5ggKdIbx", "_id": "Cc53vyYz5ggKdIbx",
"systemPath": "actions", "systemPath": "actions",
"description": "<p>When you succeed on an attack against a target within Melee range, you can <strong>spend a Hope</strong> to clamp that opponent in your jaws, making them temporarily Restrained and Vulnerable.</p>", "description": "",
"chatDisplay": true, "chatDisplay": true,
"actionType": "action", "actionType": "action",
"cost": [ "cost": [
@ -23,7 +23,7 @@
], ],
"uses": { "uses": {
"value": null, "value": null,
"max": null, "max": "",
"recovery": null "recovery": null
}, },
"damage": { "damage": {

View file

@ -19,20 +19,20 @@
"actionType": "action", "actionType": "action",
"cost": [ "cost": [
{ {
"key": "stress", "consumeOnSuccess": false,
"itemId": null,
"value": 1,
"scalable": false, "scalable": false,
"step": null, "key": "stress",
"consumeOnSuccess": false "value": 1,
"itemId": null,
"step": null
}, },
{ {
"key": "resource", "consumeOnSuccess": false,
"itemId": "n0P3VS1WfxvmXbB6",
"value": 1,
"scalable": false, "scalable": false,
"step": null, "key": "resource",
"consumeOnSuccess": false "value": 1,
"itemId": "n0P3VS1WfxvmXbB6",
"step": null
} }
], ],
"uses": { "uses": {
@ -119,16 +119,17 @@
"cost": [ "cost": [
{ {
"scalable": false, "scalable": false,
"key": "hitPoints", "key": "resource",
"value": 1, "value": 1,
"itemId": "n0P3VS1WfxvmXbB6",
"step": null, "step": null,
"consumeOnSuccess": false "consumeOnSuccess": false
} }
], ],
"uses": { "uses": {
"value": null, "value": null,
"max": "1", "max": "",
"recovery": "shortRest" "recovery": null
}, },
"effects": [], "effects": [],
"target": { "target": {
@ -140,7 +141,15 @@
"range": "" "range": ""
} }
}, },
"resource": null, "resource": {
"type": "simple",
"value": 1,
"max": "1",
"recovery": "shortRest",
"progression": "decreasing",
"dieFaces": "d4",
"icon": "fa-solid fa-seedling"
},
"attribution": { "attribution": {
"source": "Daggerheart SRD", "source": "Daggerheart SRD",
"page": 130, "page": 130,

View file

@ -52,7 +52,7 @@
"includeBase": false "includeBase": false
}, },
"target": { "target": {
"type": "hostile", "type": "",
"amount": 1 "amount": 1
}, },
"effects": [ "effects": [
@ -63,13 +63,23 @@
], ],
"name": "Fire", "name": "Fire",
"img": "icons/magic/fire/barrier-wall-explosion-orange.webp", "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": { "xDBLH5TWidvrYF7z": {
"type": "effect", "type": "effect",
"_id": "xDBLH5TWidvrYF7z", "_id": "xDBLH5TWidvrYF7z",
"systemPath": "actions", "systemPath": "actions",
"description": "<p>When an adversary marks 1 or more Hit Points, they must also mark a Stress.</p>", "description": "<p>Your allies gain a +1 bonus to Strength.</p>",
"chatDisplay": true, "chatDisplay": true,
"actionType": "action", "actionType": "action",
"cost": [], "cost": [],
@ -78,19 +88,26 @@
"max": "", "max": "",
"recovery": null "recovery": null
}, },
"effects": [ "effects": [],
{
"_id": "WRuijfHxmUscAa69",
"onSave": false
}
],
"target": { "target": {
"type": "friendly", "type": "",
"amount": null "amount": null
}, },
"name": "Earth", "name": "Earth",
"img": "icons/magic/control/buff-strength-muscle-damage-red.webp", "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": { "S4t5HlgxWlHwaBDw": {
"type": "effect", "type": "effect",
@ -112,12 +129,22 @@
} }
], ],
"target": { "target": {
"type": "hostile", "type": "self",
"amount": null "amount": null
}, },
"name": "Water", "name": "Water",
"img": "icons/magic/water/vortex-water-whirlpool.webp", "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": { "hAsKFFewtTqd1gg9": {
"type": "attack", "type": "attack",
@ -138,15 +165,10 @@
"includeBase": false "includeBase": false
}, },
"target": { "target": {
"type": "any", "type": "self",
"amount": null "amount": null
}, },
"effects": [ "effects": [],
{
"_id": "mJBA2QTyM9SM5NVS",
"onSave": false
}
],
"roll": { "roll": {
"type": "diceSet", "type": "diceSet",
"trait": null, "trait": null,
@ -169,7 +191,19 @@
}, },
"name": "Air", "name": "Air",
"img": "icons/magic/air/air-burst-spiral-blue-gray.webp", "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, "originItemType": null,
@ -181,43 +215,6 @@
} }
}, },
"effects": [ "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": {
"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)", "name": "Elemental Aura (Water)",
"img": "icons/magic/water/vortex-water-whirlpool.webp", "img": "icons/magic/water/vortex-water-whirlpool.webp",
@ -230,13 +227,10 @@
}, },
"disabled": false, "disabled": false,
"duration": { "duration": {
"startTime": null, "value": null,
"combat": null, "units": "seconds",
"seconds": null, "expiry": null,
"rounds": null, "expired": false
"turns": null,
"startRound": null,
"startTurn": null
}, },
"description": "", "description": "",
"tint": "#ffffff", "tint": "#ffffff",
@ -246,6 +240,9 @@
"_stats": { "_stats": {
"compendiumSource": null "compendiumSource": null
}, },
"start": null,
"showIcon": 1,
"folder": null,
"_key": "!items.effects!2JH9NaOh69yN80Gw.H7W52ps5d3UGmaFr" "_key": "!items.effects!2JH9NaOh69yN80Gw.H7W52ps5d3UGmaFr"
}, },
{ {
@ -260,13 +257,10 @@
}, },
"disabled": false, "disabled": false,
"duration": { "duration": {
"startTime": null, "value": null,
"combat": null, "units": "seconds",
"seconds": null, "expiry": null,
"rounds": null, "expired": false
"turns": null,
"startRound": null,
"startTurn": null
}, },
"description": "", "description": "",
"tint": "#ffffff", "tint": "#ffffff",
@ -276,37 +270,114 @@
"_stats": { "_stats": {
"compendiumSource": null "compendiumSource": null
}, },
"start": null,
"showIcon": 1,
"folder": null,
"_key": "!items.effects!2JH9NaOh69yN80Gw.WX5AMEpmUAutB9Hm" "_key": "!items.effects!2JH9NaOh69yN80Gw.WX5AMEpmUAutB9Hm"
}, },
{ {
"name": "Elemental Aura (Air)", "name": "Elemental Aura: Earth",
"img": "icons/magic/air/air-burst-spiral-blue-gray.webp",
"origin": "Compendium.daggerheart.subclasses.Item.2JH9NaOh69yN80Gw",
"transfer": false,
"_id": "mJBA2QTyM9SM5NVS",
"type": "base",
"system": {
"changes": []
},
"disabled": false, "disabled": false,
"duration": { "img": "icons/magic/control/buff-strength-muscle-damage-red.webp",
"startTime": null, "description": "<p>Your allies gain a +1 bonus to Strength.</p>",
"combat": null, "transfer": false,
"seconds": null,
"rounds": null,
"turns": null,
"startRound": null,
"startTurn": null
},
"description": "",
"tint": "#ffffff",
"statuses": [], "statuses": [],
"system": {
"changes": [
{
"key": "system.traits.strength.value",
"type": "add",
"value": 1,
"priority": null,
"phase": "initial"
}
],
"duration": {
"description": "",
"type": ""
},
"stacking": null,
"targetDispositions": [
1
]
},
"_id": "yvpWNCNobg0LLjey",
"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, "sort": 0,
"flags": {}, "flags": {},
"_stats": { "_stats": {
"compendiumSource": null "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": "<p><span style=\"color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.753);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;display:inline !important;float:none\">When you or an ally takes damage from an attack beyond Melee range, reduce the damage by 1d8.</span></p>",
"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, "sort": 100000,

View file

@ -261,13 +261,14 @@
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;
&.glassy { &.glassy {
background-color: light-dark(@dark-blue-10, @golden-10); background-color: light-dark(@dark-blue-10, @golden-10);
border-color: transparent; border: none;
legend { legend {
padding: 2px 12px; padding: 2px 12px;
@ -388,6 +389,10 @@
justify-content: space-between; justify-content: space-between;
} }
label {
white-space: nowrap;
}
.btn { .btn {
padding-top: 15px; padding-top: 15px;
} }
@ -403,6 +408,13 @@
> .checkbox { > .checkbox {
align-self: end; align-self: end;
} }
.auto-sized {
width: auto;
display: flex;
flex-direction: column;
align-items: center;
}
} }
.form-group { .form-group {
@ -539,50 +551,39 @@
transform-origin: top; transform-origin: top;
} }
.item-buttons { /* A multi-button element used to attach resources to other buttons */
grid-column: span 3; .item-button {
display: flex; display: flex;
gap: 8px; button {
flex-wrap: wrap; color: light-dark(@dark-blue, @dark-blue);
margin-top: 2px; white-space: nowrap;
border: none;
.item-button { &:hover {
display: flex; color: @color-text-emphatic;
border: 1px solid light-dark(#18162e, #18162e); }
color: light-dark(#18162e, #18162e);
outline: none;
box-shadow: none;
border-radius: 6px;
button { &:not(:first-child) {
border-radius: 3px 0px 0px 3px; border-top-left-radius: 0;
color: light-dark(@dark-blue, @dark-blue); border-bottom-left-radius: 0;
white-space: nowrap; padding: 6px;
border: 0; background: light-dark(@dark-blue-10, @golden-secondary);
color: light-dark(@dark-blue, @dark-golden);
&:hover { &:hover {
color: @color-text-emphatic; background: light-dark(@light-black, @dark-blue);
} color: light-dark(@dark-blue, @golden-secondary);
&: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);
}
} }
} }
.spacer { &:not(:last-child) {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
border-right: 1px solid black; border-right: 1px solid black;
content: '';
} }
} }
} }
.artist-attribution { .artist-attribution {
width: 100%; width: 100%;
display: flex; display: flex;

View file

@ -12,6 +12,9 @@
} }
.daggerheart.dh-style { .daggerheart.dh-style {
/** Not an actual scrollbar width (it can't be configured on all browsers) but actually a compensation value for scrollbar gutter purposes */
--scrollbar-width: 10px;
* { * {
scrollbar-width: thin; scrollbar-width: thin;
scrollbar-color: light-dark(@dark-blue, @golden) transparent; scrollbar-color: light-dark(@dark-blue, @golden) transparent;

View file

@ -43,16 +43,19 @@
} }
} }
.item-main {
border-radius: 5px;
padding: 2px;
margin: -2px;
}
&:hover { &:hover {
.inventory-item-header .item-label .item-name .expanded-icon { .inventory-item-header .item-label .item-name .expanded-icon {
margin-left: 10px; margin-left: 10px;
display: inline-block; display: inline-block;
} }
&:has(.inventory-item-content.extensible) { .item-main {
.inventory-item-header, background: light-dark(@dark-blue-40, @golden-40);
.inventory-item-content {
background: light-dark(@dark-blue-40, @golden-40);
}
} }
&:has(.inventory-item-content.extended) { &:has(.inventory-item-content.extended) {
.inventory-item-header .item-label .item-name .expanded-icon { .inventory-item-header .item-label .item-name .expanded-icon {
@ -60,19 +63,6 @@
} }
} }
} }
&:has(.inventory-item-content.extensible) {
.inventory-item-header {
border-radius: 5px 5px 0 0;
}
.inventory-item-content {
border-radius: 0 0 5px 5px;
}
}
&:not(:has(.inventory-item-content.extensible)) .inventory-item-header {
border-radius: 5px;
}
} }
.inventory-item-header, .inventory-item-header,
@ -171,7 +161,7 @@
grid-template-rows: 1fr; grid-template-rows: 1fr;
padding-top: 4px; padding-top: 4px;
} }
.invetory-description { .inventory-description {
overflow: hidden; overflow: hidden;
h1 { h1 {
@ -281,6 +271,14 @@
grid-area: labels; grid-area: labels;
} }
} }
.item-buttons {
grid-column: span 3;
display: flex;
gap: 4px;
flex-wrap: wrap;
margin-top: 2px;
}
} }
.card-item { .card-item {

View file

@ -1,5 +1,6 @@
@import '../utils/colors.less'; @import '../utils/colors.less';
@import '../utils/fonts.less'; @import '../utils/fonts.less';
@import '../utils/mixin.less';
.application.daggerheart { .application.daggerheart {
prose-mirror { prose-mirror {
@ -12,6 +13,7 @@
background-color: transparent; background-color: transparent;
} }
.editor-content { .editor-content {
.with-scroll-shadows();
h1 { h1 {
font-size: var(--font-size-32); font-size: var(--font-size-32);
} }

View file

@ -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 { .status-bar {
display: flex; display: flex;
justify-content: center; flex-direction: column;
align-items: center;
position: relative; 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 { .status-value {
position: absolute; position: relative;
display: flex; display: flex;
padding: 0 5px; padding: 0 5px;
font-size: 1.5rem; font-size: 1.5rem;
align-items: center; align-items: center;
width: 140px; width: 100px;
height: 40px; height: 40px;
justify-content: center; justify-content: center;
text-align: center; text-align: center;
z-index: 2; z-index: 2;
color: @beige; color: @beige;
> * {
z-index: 1;
}
input[type='number'] { input[type='number'] {
background: transparent; background: transparent;
font-size: 1.5rem; font-size: 1.5rem;
@ -146,11 +143,11 @@
.progress-bar { .progress-bar {
position: absolute; position: absolute;
appearance: none; appearance: none;
width: 100px; width: 100%;
height: 40px; height: 100%;
border: 1px solid @color-border; border: 1px solid @color-border;
border-radius: 6px; border-radius: 6px;
z-index: 1; z-index: 0;
background: @dark-blue; background: @dark-blue;
&::-webkit-progress-bar { &::-webkit-progress-bar {
@ -175,4 +172,29 @@
border-radius: 6px; 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;
} }

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);
@ -54,6 +68,37 @@
} }
} }
.tab.notes.active {
padding: 0;
margin: 0;
scrollbar-gutter: unset;
// Add padding around top level level prosemirrors used for note tabs
> prose-mirror {
@right-padding: calc(16px - var(--scrollbar-width));
.editor-content {
scrollbar-gutter: stable;
padding-right: @right-padding;
padding-bottom: 4px;
}
&.inactive {
button.toggle {
top: 16px;
}
.editor-content {
padding: 16px @right-padding 4px 16px;
}
}
&.active {
padding: 8px 0 0 16px;
}
}
.artist-attribution {
padding-left: 16px;
}
}
.search-section { .search-section {
display: flex; display: flex;
gap: 10px; gap: 10px;

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

@ -1,7 +1,6 @@
@import './sheet.less';
@import './features.less'; @import './features.less';
@import './header.less'; @import './header.less';
@import './sheet.less';
@import './sidebar.less'; @import './sidebar.less';
@import './effects.less'; @import './effects.less';
@import './notes.less'; @import './notes.less';

View file

@ -2,35 +2,37 @@
@import '../../../utils/fonts.less'; @import '../../../utils/fonts.less';
.application.sheet.daggerheart.actor.dh-style.adversary { .application.sheet.daggerheart.actor.dh-style.adversary {
--sidebar-width: 260px;
.window-content { .window-content {
display: grid; display: grid;
grid-template-columns: 275px 1fr; grid-template-columns: var(--sidebar-width) 1fr;
grid-template-rows: auto 1fr; grid-template-rows: auto 1fr;
height: 100%; height: 100%;
width: 100%; width: 100%;
padding-bottom: 0; padding-bottom: 0;
}
.adversary-sidebar-sheet { .adversary-sidebar-sheet {
grid-row: 1 / span 2; grid-row: 1 / span 2;
grid-column: 1; grid-column: 1;
overflow: hidden; 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; display: flex;
flex-direction: column; flex-direction: column;
} margin: 0 0 10px 0;
.adversary-header-sheet {
grid-row: 1;
grid-column: 2;
}
.tab {
grid-row: 2;
grid-column: 2;
&.active {
overflow: hidden;
display: flex;
flex-direction: column;
}
} }
} }
} }

View file

@ -22,8 +22,8 @@
.application.sheet.daggerheart.actor.dh-style.adversary { .application.sheet.daggerheart.actor.dh-style.adversary {
.adversary-sidebar-sheet { .adversary-sidebar-sheet {
width: 275px; width: var(--sidebar-width);
min-width: 275px; min-width: var(--sidebar-width);
border-right: 1px solid light-dark(@dark-blue, @golden); border-right: 1px solid light-dark(@dark-blue, @golden);
.portrait { .portrait {
@ -52,7 +52,7 @@
} }
img { img {
height: 275px; height: var(--sidebar-width);
} }
.death-roll-btn { .death-roll-btn {
@ -63,7 +63,7 @@
.threshold-section { .threshold-section {
position: relative; position: relative;
display: flex; display: flex;
gap: 10px; gap: 7px;
background-color: light-dark(transparent, @dark-blue); background-color: light-dark(transparent, @dark-blue);
color: @color-text-emphatic; color: @color-text-emphatic;
padding: 5px 10px; padding: 5px 10px;
@ -106,8 +106,8 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
top: -20px; top: -20px;
gap: 16px; gap: 10px;
margin-bottom: -10px; margin-bottom: -16px;
&.pip-display { &.pip-display {
top: -15px; top: -15px;
@ -120,105 +120,6 @@
.resources-section { .resources-section {
display: flex; display: flex;
justify-content: space-evenly; 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 { .status-section {
@ -256,7 +157,7 @@
.status-label { .status-label {
padding: 2px 10px; padding: 2px 10px;
width: 100%; width: 100%;
border-radius: 3px; border-radius: 0 0 3px 3px;
background: light-dark(@dark-blue, @golden); background: light-dark(@dark-blue, @golden);
h4 { h4 {
@ -284,8 +185,8 @@
.shortcut-items-section { .shortcut-items-section {
overflow-y: hidden; overflow-y: hidden;
padding-top: 10px; padding: 10px 0;
padding-bottom: 20px; flex: 1;
scrollbar-gutter: stable; scrollbar-gutter: stable;
.with-scroll-shadows(); .with-scroll-shadows();
@ -313,8 +214,6 @@
} }
.experience-section { .experience-section {
margin-bottom: 20px;
.title { .title {
display: flex; display: flex;
gap: 15px; gap: 15px;
@ -331,21 +230,21 @@
gap: 5px; gap: 5px;
width: 100%; width: 100%;
margin-top: 10px; margin-top: 10px;
align-items: center; align-items: stretch;
padding: 0 4px 0 12px;
.experience-row { .experience-row {
display: flex; display: flex;
gap: 5px; gap: 5px;
width: 250px;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
.experience-name { .experience-name {
width: 180px;
display: flex; display: flex;
align-items: center; align-items: center;
text-align: start; text-align: start;
font-size: var(--font-size-14); font-size: var(--font-size-14);
flex: 1;
color: light-dark(@dark, @beige); color: light-dark(@dark, @beige);
line-height: 1; line-height: 1;
} }
@ -370,9 +269,7 @@
.reaction-section { .reaction-section {
display: flex; display: flex;
padding: 0 10px; margin: 10px;
margin-top: 20px;
width: 100%;
button { button {
height: 40px; height: 40px;

View file

@ -9,16 +9,13 @@
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

@ -8,10 +8,8 @@
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

@ -89,113 +89,13 @@
.resources-section { .resources-section {
justify-content: space-around; justify-content: space-around;
margin: 8px 2px 8px 2px; margin: 8px 2px 0 2px;
} }
} }
.resources-section { .resources-section {
display: flex; display: flex;
justify-content: space-evenly; 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 { .status-section {
@ -245,21 +145,21 @@
.status-bar.armor-slots { .status-bar.armor-slots {
display: flex; display: flex;
justify-content: center;
position: relative;
width: 95px; width: 95px;
height: 30px;
white-space: nowrap; white-space: nowrap;
.status-label {
height: 30px;
}
.status-label { .status-label {
padding: 2px 2px; padding: 2px 2px;
position: relative; position: relative;
top: 30px;
height: 22px; height: 22px;
width: 95px; width: 95px;
border-radius: 3px; border-radius: 3px;
background: light-dark(@dark-blue, @golden); background: light-dark(@dark-blue, @golden);
clip-path: none;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@ -291,40 +191,16 @@
} }
} }
.slot-value { .slot-value {
position: absolute;
display: flex;
padding: 0 5px;
font-size: 1.2rem; font-size: 1.2rem;
align-items: center;
width: 80px; width: 80px;
height: 30px; height: 30px;
justify-content: center;
text-align: center;
z-index: 2;
color: light-dark(@dark-blue, @beige); color: light-dark(@dark-blue, @beige);
flex-direction: column; flex-direction: column;
.slot-bar { .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; background: @dark-blue;
justify-content: center; justify-content: center;
color: @color-text-emphatic; border-bottom: none;
.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-label { .slot-label {
display: flex; display: flex;
@ -364,41 +240,21 @@
} }
} }
.status-value { .status-value {
position: absolute;
display: flex;
padding: 0 6px; padding: 0 6px;
font-size: 1.2rem; font-size: 1.2rem;
align-items: center;
width: 80px; width: 80px;
height: 30px; height: 30px;
justify-content: center; justify-content: center;
text-align: center;
z-index: 2;
color: light-dark(@dark-blue, @beige); color: light-dark(@dark-blue, @beige);
border: 1px solid @color-border;
border-bottom: none;
border-radius: 6px 6px 0 0;
input[type='number'] { input[type='number'] {
background: transparent;
font-size: 1.2rem; font-size: 1.2rem;
width: 30px; width: 30px;
height: 20px; height: 20px;
text-align: center;
border: none;
outline: 2px solid transparent;
color: light-dark(@dark-blue, @beige); color: light-dark(@dark-blue, @beige);
&.bar-input { &.bar-input {
padding: 0;
color: light-dark(@dark-blue, @beige); 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 { .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); background: light-dark(transparent, @dark-blue);
border-bottom: none; border-bottom: none;
border-radius: 6px 6px 0 0; 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;
}
} }
} }

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

@ -8,10 +8,8 @@
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: 4px;
.with-scroll-shadows();
} }
} }
} }

View file

@ -3,7 +3,28 @@
@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 {
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 { .actors-list {
display: flex; display: flex;
@ -206,42 +227,12 @@
} }
.slot-bar { .slot-bar {
display: flex;
align-items: center; align-items: center;
flex-wrap: wrap; flex-wrap: wrap;
gap: 4px;
background-color: light-dark(@dark-blue-10, @dark-blue); background-color: light-dark(@dark-blue-10, @dark-blue);
color: @color-text-emphatic;
padding: 2px 5px; padding: 2px 5px;
border: 1px solid @color-border;
border-radius: 0 6px 6px 0; border-radius: 0 6px 6px 0;
width: fit-content; 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);
}
}
} }
} }
} }

View file

@ -17,35 +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;
}
.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;
}
}
} }
} }

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.0", "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.0/system.zip", "download": "https://github.com/Foundryborne/daggerheart/releases/download/2.5.0/system.zip",
"authors": [ "authors": [
{ {
"name": "WBHarry" "name": "WBHarry"

View file

@ -11,9 +11,10 @@
<a class="btn" data-tooltip="{{localize "CONTROLS.CommonDelete"}}" data-action="removeElement" data-index="{{index}}"><i class="fas fa-trash"></i></a> <a class="btn" data-tooltip="{{localize "CONTROLS.CommonDelete"}}" data-action="removeElement" data-index="{{index}}"><i class="fas fa-trash"></i></a>
</div> </div>
<div class="nest-inputs"> <div class="nest-inputs">
{{formField ../fields.type value=area.type name=(concat "areas." index ".type") 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}} {{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}} {{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" }}
</div> </div>
<div class="sub-section-header"> <div class="sub-section-header">

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,13 +1,9 @@
<section <section
class='tab {{tabs.notes.cssClass}} {{tabs.notes.id}}' class="tab {{tabs.notes.cssClass}} {{tabs.notes.id}}"
data-tab='{{tabs.notes.id}}' data-tab="{{tabs.notes.id}}"
data-group='{{tabs.notes.group}}' data-group="{{tabs.notes.group}}"
> >
<fieldset class="fit-height"> {{formInput notes.field value=notes.value enriched=notes.enriched toggled=true}}
<legend>{{localize tabs.notes.label}}</legend>
{{formInput notes.field value=notes.value enriched=notes.enriched toggled=true}}
</fieldset>
{{#if (and showAttribution document.system.attribution.artist)}} {{#if (and showAttribution document.system.attribution.artist)}}
<label class="artist-attribution">{{localize "DAGGERHEART.GENERAL.artistAttribution" artist=document.system.attribution.artist}}</label> <label class="artist-attribution">{{localize "DAGGERHEART.GENERAL.artistAttribution" artist=document.system.attribution.artist}}</label>
{{/if}} {{/if}}

View file

@ -92,9 +92,11 @@
{{/each}} {{/each}}
</div> </div>
</div> </div>
<line-div></line-div> </div>
<div class="reaction-section"> <div class="reaction-section">
<button type="button" data-action="reactionRoll">{{localize "DAGGERHEART.GENERAL.Roll.reaction"}}</button> <button type="button" data-action="reactionRoll">
</div> <i class="fa-solid fa-dice"></i>
{{localize "DAGGERHEART.GENERAL.Roll.reaction"}}
</button>
</div> </div>
</aside> </aside>

View file

@ -79,8 +79,6 @@
{{/if}} {{/if}}
</div> </div>
{{/if}} {{/if}}
</div> </div>
<div class="character-row"> <div class="character-row">

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'
@ -31,7 +30,6 @@
collection=@root.inventory.armor collection=@root.inventory.armor
isGlassy=true isGlassy=true
canCreate=@root.editable canCreate=@root.editable
hideResources=true
}} }}
{{> 'daggerheart.inventory-items' {{> 'daggerheart.inventory-items'
title='TYPES.Item.consumable' title='TYPES.Item.consumable'

View file

@ -23,9 +23,9 @@
<div class="status-bar armor-slots"> <div class="status-bar armor-slots">
{{#if useResourcePips}} {{#if useResourcePips}}
<div class='slot-value'> <div class='slot-value'>
<div class="slot-bar"> <div class="slot-bar armor">
{{#times document.system.armorScore.max}} {{#times document.system.armorScore.max}}
<a class='armor-slot' data-action='toggleArmor' data-value="{{add this 1}}"> <a class="slot" data-action="toggleArmor" data-value="{{add this 1}}">
{{#if (gte ../document.system.armorScore.value (add this 1))}} {{#if (gte ../document.system.armorScore.value (add this 1))}}
<i class="fa-solid fa-shield"></i> <i class="fa-solid fa-shield"></i>
{{else}} {{else}}
@ -44,15 +44,15 @@
</div> </div>
{{else}} {{else}}
<div class='status-value'> <div class='status-value'>
<progress
class='progress-bar stress-color'
value='{{document.system.armorScore.value}}'
max='{{document.system.armorScore.max}}'
></progress>
<input class="bar-input armor-marks-input" value="{{document.system.armorScore.value}}" type="number" id="{{document.uuid}}-armor-slots"> <input class="bar-input armor-marks-input" value="{{document.system.armorScore.value}}" type="number" id="{{document.uuid}}-armor-slots">
<span>/</span> <span>/</span>
<span class="bar-label">{{document.system.armorScore.max}}</span> <span class="bar-label">{{document.system.armorScore.max}}</span>
</div> </div>
<progress
class='progress-bar stress-color'
value='{{document.system.armorScore.value}}'
max='{{document.system.armorScore.max}}'
></progress>
<a class="status-label" data-action="toggleArmorMangement" {{disabled (not @root.editable)}}> <a class="status-label" data-action="toggleArmorMangement" {{disabled (not @root.editable)}}>
<h4>{{localize "DAGGERHEART.GENERAL.armorSlots"}}</h4> <h4>{{localize "DAGGERHEART.GENERAL.armorSlots"}}</h4>
{{#if @root.editable}}<i class="fa-solid fa-gear" inert></i>{{/if}} {{#if @root.editable}}<i class="fa-solid fa-gear" inert></i>{{/if}}

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

@ -3,10 +3,7 @@
data-tab='{{tabs.notes.id}}' data-tab='{{tabs.notes.id}}'
data-group='{{tabs.notes.group}}' data-group='{{tabs.notes.group}}'
> >
<fieldset class="fit-height"> {{formInput notes.field value=notes.value enriched=notes.value toggled=true}}
<legend>{{localize tabs.notes.label}}</legend>
{{formInput notes.field value=notes.value enriched=notes.value toggled=true}}
</fieldset>
{{#if (and showAttribution document.system.attribution.artist)}} {{#if (and showAttribution document.system.attribution.artist)}}
<label class="artist-attribution">{{localize "DAGGERHEART.GENERAL.artistAttribution" artist=document.system.attribution.artist}}</label> <label class="artist-attribution">{{localize "DAGGERHEART.GENERAL.artistAttribution" artist=document.system.attribution.artist}}</label>

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,7 +1,7 @@
<section <section
class='tab {{tabs.notes.cssClass}} {{tabs.notes.id}}' class="tab {{tabs.notes.cssClass}} {{tabs.notes.id}}"
data-tab='{{tabs.notes.id}}' data-tab="{{tabs.notes.id}}"
data-group='{{tabs.notes.group}}' data-group="{{tabs.notes.group}}"
> >
{{formInput notes.field value=notes.value enriched=notes.enriched toggled=true}} {{formInput notes.field value=notes.value enriched=notes.enriched toggled=true}}

View file

@ -1,10 +1,7 @@
<section <section
class='tab {{tabs.notes.cssClass}} {{tabs.notes.id}}' class="tab {{tabs.notes.cssClass}} {{tabs.notes.id}}"
data-tab='{{tabs.notes.id}}' data-tab="{{tabs.notes.id}}"
data-group='{{tabs.notes.group}}' data-group="{{tabs.notes.group}}"
> >
<fieldset class="fit-height"> {{formInput notes.field value=notes.value enriched=notes.value toggled=true}}
<legend>{{localize tabs.notes.label}}</legend>
{{formInput notes.field value=notes.value enriched=notes.value toggled=true}}
</fieldset>
</section> </section>

View file

@ -135,9 +135,9 @@
<span class="max">{{member.armorScore.max}}</span> <span class="max">{{member.armorScore.max}}</span>
</span> </span>
</div> </div>
<div class="slot-bar"> <div class="slot-bar armor">
{{#times member.armorScore.max}} {{#times member.armorScore.max}}
<a class='armor-slot' data-action='toggleArmorSlot' data-actor-id="{{member.uuid}}" data-value="{{add this 1}}"> <a class="slot" data-action="toggleArmorSlot" data-actor-id="{{member.uuid}}" data-value="{{add this 1}}">
{{#if (gte member.armorScore.value (add this 1))}} {{#if (gte member.armorScore.value (add this 1))}}
<i class="fa-solid fa-shield"></i> <i class="fa-solid fa-shield"></i>
{{else}} {{else}}

View file

@ -25,146 +25,145 @@ Parameters:
data-type="{{type}}" data-item-type="{{item.type}}" data-type="{{type}}" data-item-type="{{item.type}}"
data-item-uuid="{{item.uuid}}" data-no-compendium-edit="{{noCompendiumEdit}}" data-item-uuid="{{item.uuid}}" data-no-compendium-edit="{{noCompendiumEdit}}"
> >
<div class="inventory-item-header {{#if hideContextMenu}}padded{{/if}}" {{#unless noExtensible}}data-action="toggleExtended" {{/unless}}> <div class="item-main">
{{!-- Image --}} <div class="inventory-item-header{{#if hideContextMenu}} padded{{/if}}" {{#unless (or noExtensible (not item.hasDescription))}}data-action="toggleExtended" {{/unless}}>
<div class="img-portait" draggable="true" {{!-- Image --}}
{{#unless (eq showActions false)}}data-action='{{ifThen item.usable "useItem" (ifThen (hasProperty item "toChat" ) "toChat" "editDoc" ) }}'{{/unless}} <div class="img-portait" draggable="true"
{{#unless hideTooltip}} {{#if (eq type 'attack' )}} data-tooltip="#attack#{{item.actor.uuid}}" {{else}} data-tooltip="#item#{{item.uuid}}" {{/if}} {{/unless}}> {{#unless (eq showActions false)}}data-action='{{ifThen item.usable "useItem" (ifThen (hasProperty item "toChat" ) "toChat" "editDoc" ) }}'{{/unless}}
<img src="{{item.img}}" class="item-img {{#if isActor}}actor-img{{/if}}" /> {{#unless hideTooltip}} {{#if (eq type 'attack' )}} data-tooltip="#attack#{{item.actor.uuid}}" {{else}} data-tooltip="#item#{{item.uuid}}" {{/if}} {{/unless}}>
{{#if (and item.usable (ne showActions false))}} <img src="{{item.img}}" class="item-img {{#if isActor}}actor-img{{/if}}" />
{{#if @root.isNPC}} {{#if (and item.usable (ne showActions false))}}
<img class="roll-img d20" src="systems/daggerheart/assets/icons/dice/default/d20.svg" alt="d20"> {{#if @root.isNPC}}
{{else}} <img class="roll-img d20" src="systems/daggerheart/assets/icons/dice/default/d20.svg" alt="d20">
<img class="roll-img duality" src="systems/daggerheart/assets/icons/dice/duality/DualityBW.svg" alt="2d12"> {{else}}
{{/if}} <img class="roll-img duality" src="systems/daggerheart/assets/icons/dice/duality/DualityBW.svg" alt="2d12">
{{/if}}
</div>
{{!-- Name & Tags --}}
<div class="item-label" draggable="true">
{{!-- Item Name --}}
<span class="item-name">{{localize item.name}} {{#unless (or noExtensible (not item.system.description))}}<span class="expanded-icon"><i class="fa-solid fa-expand"></i></span>{{/unless}}</span>
{{!-- Tags Start --}}
{{#if (not hideTags)}}
{{#> "systems/daggerheart/templates/sheets/global/partials/item-tags.hbs" item}}
{{#if (eq ../type 'feature')}}
{{#if (and system.featureForm (ne @root.document.type "character"))}}
<div class="tag feature-form">
<span class="recall-value">{{localize (concat "DAGGERHEART.CONFIG.FeatureForm." system.featureForm)}}</span>
</div>
{{/if}} {{/if}}
{{/if}} {{/if}}
{{/ "systems/daggerheart/templates/sheets/global/partials/item-tags.hbs"}} </div>
{{/if}}
{{!--Tags End --}} {{!-- Name & Tags --}}
</div> <div class="item-label" draggable="true">
{{!-- Item Name --}}
<span class="item-name">{{localize item.name}} {{#unless (or noExtensible (not item.hasDescription))}}<span class="expanded-icon"><i class="fa-solid fa-expand"></i></span>{{/unless}}</span>
{{!-- Simple Resource --}} {{!-- Tags Start --}}
{{#if (and (not hideResources) (not (eq item.system.resource.type 'diceValue')))}} {{#if (not hideTags)}}
{{> "systems/daggerheart/templates/sheets/global/partials/item-resource.hbs"}} {{#> "systems/daggerheart/templates/sheets/global/partials/item-tags.hbs" item}}
{{/if}} {{#if (and (eq ../type 'feature') system.featureForm (ne @root.document.type "character"))}}
{{#if (or isQuantifiable (or (eq item.system.quantity 0) (gt item.system.quantity 1)))}} <div class="tag feature-form">
<div class="item-resource"> <span class="recall-value">{{localize (concat "DAGGERHEART.CONFIG.FeatureForm." system.featureForm)}}</span>
<input type="number" id="{{item.uuid}}-quantity" class="inventory-item-quantity" value="{{item.system.quantity}}" min="0" /> </div>
{{/if}}
{{/ "systems/daggerheart/templates/sheets/global/partials/item-tags.hbs"}}
{{/if}}
{{!--Tags End --}}
</div>
{{!-- Simple Resource --}}
{{#if (and (not hideResources) (not (eq item.system.resource.type 'diceValue')))}}
{{> "systems/daggerheart/templates/sheets/global/partials/item-resource.hbs"}}
{{/if}}
{{#if (or isQuantifiable (or (eq item.system.quantity 0) (gt item.system.quantity 1)))}}
<div class="item-resource">
<input type="number" id="{{item.uuid}}-quantity" class="inventory-item-quantity" value="{{item.system.quantity}}" min="0" />
</div>
{{/if}}
{{!-- Controls --}}
{{#unless hideControls}}
<div class="controls">
{{!-- Toggle/Equip buttons --}}
{{#if @root.editable}}
{{#if (and (eq actorType 'character') (eq type 'weapon'))}}
<a class="{{#unless item.system.equipped}}unequipped{{/unless}}" data-action="toggleEquipItem"
data-tooltip="DAGGERHEART.UI.Tooltip.{{ifThen item.system.equipped 'unequip' 'equip' }}">
<i class="fa-solid fa-hands" inert></i>
</a>
{{/if}}
{{#if (and (eq actorType 'character') (eq type 'armor'))}}
<a class="{{#unless item.system.equipped}}unequipped{{/unless}}" data-action="toggleEquipItem"
data-tooltip="DAGGERHEART.UI.Tooltip.{{ifThen item.system.equipped 'unequip' 'equip' }}">
<i class="fa-solid fa-fw fa-shield" inert></i>
</a>
{{/if}}
{{#if (and (eq type 'domainCard'))}}
<a data-action="toggleVault"
data-tooltip="DAGGERHEART.UI.Tooltip.{{ifThen item.system.inVault 'sendToLoadout' 'sendToVault' }}">
<i class="fa-solid {{ifThen item.system.inVault 'fa-arrow-up' 'fa-arrow-down'}}" inert></i>
</a>
{{/if}}
{{#if (and (and (eq type 'effect') (not (eq item.type 'beastform'))))}}
<a data-action="toggleEffect"
data-tooltip="DAGGERHEART.UI.Tooltip.{{ifThen item.disabled 'enableEffect' 'disableEffect' }}">
<i class="{{ifThen item.disabled 'fa-solid fa-toggle-off' 'fa-solid fa-toggle-on'}}" inert></i>
</a>
{{/if}}
{{/if}}
{{!-- Send to Chat --}}
{{#if (hasProperty item "toChat")}}
<a data-action="toChat" data-tooltip="DAGGERHEART.UI.Tooltip.sendToChat">
<i class="fa-regular fa-fw fa-message" inert></i>
</a>
{{/if}}
{{!-- Document management buttons or context menu --}}
{{#if (and (not isActor) (not hideContextMenu))}}
<a data-action="triggerContextMenu" data-tooltip="DAGGERHEART.UI.Tooltip.moreOptions">
<i class="fa-solid fa-fw fa-ellipsis-vertical" inert></i>
</a>
{{else if (and @root.editable (not hideModifyControls))}}
<a data-action="editDoc" data-tooltip="DAGGERHEART.UI.Tooltip.edit">
<i class="fa-solid fa-edit" inert></i>
</a>
{{#if (not isActor)}}
<a data-action="deleteDoc" data-tooltip="DAGGERHEART.UI.Tooltip.deleteItem">
<i class="fa-solid fa-trash" inert></i>
</a>
{{else if (eq type 'adversary')}}
<a data-action='deleteAdversary' data-category="{{categoryAdversary}}" data-tooltip="CONTROLS.CommonDelete">
<i class="fas fa-trash" inert></i>
</a>
{{/if}}
{{/if}}
</div>
{{/unless}}
</div> </div>
{{#unless hideDescription}}
<div class="inventory-item-content{{#unless (or noExtensible (not item.hasDescription))}} extensible{{/unless}}">
{{!-- Description --}}
<div class="inventory-description"></div>
</div>
{{/unless}}
</div>
{{!-- Dice Resource --}}
{{#if (and (not hideResources) (eq item.system.resource.type 'diceValue'))}}
{{> "systems/daggerheart/templates/sheets/global/partials/item-resource.hbs"}}
{{/if}} {{/if}}
{{!-- Actions Buttons --}}
{{!-- Controls --}} {{#if (and showActions item.system.actions.size)}}
{{#unless hideControls}} <div class="item-buttons">
<div class="controls"> {{#each item.system.actions as | action |}}
{{!-- Toggle/Equip buttons --}} <div class="item-button">
{{#if @root.editable}} {{#if (and (eq action.type 'beastform') @root.beastformActive)}}
{{#if (and (eq actorType 'character') (eq type 'weapon'))}} <button type="button" data-action="cancelBeastform" data-item-uuid="{{action.uuid}}">
<a class="{{#unless item.system.equipped}}unequipped{{/unless}}" data-action="toggleEquipItem" <i class="fa-solid {{action.typeIcon}} action-icon"></i>
data-tooltip="DAGGERHEART.UI.Tooltip.{{ifThen item.system.equipped 'unequip' 'equip' }}"> {{localize "DAGGERHEART.ACTORS.Character.cancelBeastform"}}
<i class="fa-solid fa-hands" inert></i> </button>
</a> {{else}}
{{/if}} <button type="button" data-action="useItem" data-item-uuid="{{action.uuid}}">
{{#if (and (eq actorType 'character') (eq type 'armor'))}} <i class="fa-solid {{action.typeIcon}} action-icon"></i>
<a class="{{#unless item.system.equipped}}unequipped{{/unless}}" data-action="toggleEquipItem" {{action.name}}
data-tooltip="DAGGERHEART.UI.Tooltip.{{ifThen item.system.equipped 'unequip' 'equip' }}"> </button>
<i class="fa-solid fa-fw fa-shield" inert></i>
</a>
{{/if}}
{{#if (and (eq type 'domainCard'))}}
<a data-action="toggleVault"
data-tooltip="DAGGERHEART.UI.Tooltip.{{ifThen item.system.inVault 'sendToLoadout' 'sendToVault' }}">
<i class="fa-solid {{ifThen item.system.inVault 'fa-arrow-up' 'fa-arrow-down'}}" inert></i>
</a>
{{/if}}
{{#if (and (and (eq type 'effect') (not (eq item.type 'beastform'))))}}
<a data-action="toggleEffect"
data-tooltip="DAGGERHEART.UI.Tooltip.{{ifThen item.disabled 'enableEffect' 'disableEffect' }}">
<i class="{{ifThen item.disabled 'fa-solid fa-toggle-off' 'fa-solid fa-toggle-on'}}" inert></i>
</a>
{{/if}}
{{/if}} {{/if}}
{{#if action.uses.max}}
{{!-- Send to Chat --}} <button type="button" class="action-uses-button" data-action="increaseActionUses" data-item-uuid="{{action.uuid}}">
{{#if (hasProperty item "toChat")}} {{action.remainingUses}}/{{action.uses.max}}
<a data-action="toChat" data-tooltip="DAGGERHEART.UI.Tooltip.sendToChat"> </button>
<i class="fa-regular fa-fw fa-message" inert></i>
</a>
{{/if}}
{{!-- Document management buttons or context menu --}}
{{#if (and (not isActor) (not hideContextMenu))}}
<a data-action="triggerContextMenu" data-tooltip="DAGGERHEART.UI.Tooltip.moreOptions">
<i class="fa-solid fa-fw fa-ellipsis-vertical" inert></i>
</a>
{{else if (and @root.editable (not hideModifyControls))}}
<a data-action="editDoc" data-tooltip="DAGGERHEART.UI.Tooltip.edit">
<i class="fa-solid fa-edit" inert></i>
</a>
{{#if (not isActor)}}
<a data-action="deleteDoc" data-tooltip="DAGGERHEART.UI.Tooltip.deleteItem">
<i class="fa-solid fa-trash" inert></i>
</a>
{{else if (eq type 'adversary')}}
<a data-action='deleteAdversary' data-category="{{categoryAdversary}}" data-tooltip="CONTROLS.CommonDelete">
<i class="fas fa-trash" inert></i>
</a>
{{/if}}
{{/if}} {{/if}}
</div> </div>
{{/unless}} {{/each}}
</div>
<div class="inventory-item-content{{#unless noExtensible}} extensible{{/unless}}">
{{!-- Description --}}
{{#unless hideDescription}}
<div class="invetory-description"></div>
{{/unless}}
</div>
{{!-- Dice Resource --}}
{{#if (and (not hideResources) (eq item.system.resource.type 'diceValue'))}}
{{> "systems/daggerheart/templates/sheets/global/partials/item-resource.hbs"}}
{{/if}}
{{!-- Actions Buttons --}}
{{#if (and showActions item.system.actions.size)}}
<div class="item-buttons">
{{#each item.system.actions as | action |}}
<div class="item-button">
{{#if (and (eq action.type 'beastform') @root.beastformActive)}}
<button type="button" data-action="cancelBeastform" data-item-uuid="{{action.uuid}}">
<i class="fa-solid {{action.typeIcon}} action-icon"></i>
{{localize "DAGGERHEART.ACTORS.Character.cancelBeastform"}}
</button>
{{else}}
<button type="button" data-action="useItem" data-item-uuid="{{action.uuid}}">
<i class="fa-solid {{action.typeIcon}} action-icon"></i>
{{action.name}}
</button>
{{/if}}
{{#if action.uses.max}}
<div class="spacer"></div>
<button type="button" class="action-uses-button" data-action="increaseActionUses" data-item-uuid="{{action.uuid}}">
{{action.remainingUses}}/{{action.uses.max}}
</button>
{{/if}}
</div> </div>
{{/each}} {{/if}}
</div>
{{/if}}
</li> </li>

View file

@ -1,35 +1,35 @@
<div class="status-bar"> {{#if useResourcePips}}
{{#if useResourcePips}} <div class="slot-value">
<div class='slot-value'> <div class="slot-bar">
<div class="slot-bar"> {{#times resource.max}}
{{#times resource.max}} <span class='slot {{#if (gte ../resource.value (add this 1))}}filled{{/if}} {{#if ../largePips}}large{{/if}}' data-action="{{../action}}" data-value="{{add this 1}}">
<span class='slot {{#if (gte ../resource.value (add this 1))}}filled{{/if}} {{#if ../largePips}}large{{/if}}' data-action="{{../action}}" data-value="{{add this 1}}"> </span>
</span> {{/times}}
{{/times}}
{{#times resource.emptyPips}} {{#times resource.emptyPips}}
<span class="empty-slot"></span> <span class="empty-slot"></span>
{{/times}} {{/times}}
</div>
<div class="slot-label">
<span class="label">{{localize label}}</span>
<span class="value">{{resource.value}} / {{resource.max}}</span>
</div>
</div> </div>
{{else}} <div class="slot-label">
<span class="label">{{localize label}}</span>
<span class="value">{{resource.value}} / {{resource.max}}</span>
</div>
</div>
{{else}}
<div class="status-bar">
<div class='status-value'> <div class='status-value'>
<progress
class='progress-bar'
max='{{resource.max}}'
value='{{resource.value}}'
></progress>
<input class="bar-input" name="{{key}}" min="0" max='{{resource.max}}' <input class="bar-input" name="{{key}}" min="0" max='{{resource.max}}'
value="{{resource.value}}" type="number"> value="{{resource.value}}" type="number">
<span>/</span> <span>/</span>
<span class="bar-label">{{resource.max}}</span> <span class="bar-label">{{resource.max}}</span>
</div> </div>
<progress <div class="status-label pointy">
class='progress-bar'
max='{{resource.max}}'
value='{{resource.value}}'
></progress>
<div class="status-label">
<h4>{{localize label}}</h4> <h4>{{localize label}}</h4>
</div> </div>
{{/if}} </div>
</div> {{/if}}

View file

@ -19,4 +19,6 @@
{{ formField systemFields.baseThresholds.fields.severe value=source.system.baseThresholds.severe label=(localize "DAGGERHEART.ITEMS.Armor.baseThresholds.severe") }} {{ formField systemFields.baseThresholds.fields.severe value=source.system.baseThresholds.severe label=(localize "DAGGERHEART.ITEMS.Armor.baseThresholds.severe") }}
</div> </div>
</fieldset> </fieldset>
{{> "systems/daggerheart/templates/sheets/global/partials/resource-section/resource-section.hbs" }}
</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>

View file

@ -6,18 +6,13 @@
<div class="message-sub-header-container"> <div class="message-sub-header-container">
{{#if message.title}} {{#if message.title}}
<h4>{{message.title}}</h4> <h4>{{message.title}}</h4>
<div>{{actor.name}} {{#if author.isGM}}(GM){{/if}}</div> <div>{{alias}} {{#if author.isGM}}(GM){{/if}}</div>
{{else}} {{else}}
{{#unless actor.name}} {{#unless actor.name}}
<h4>{{author.name}}</h4> <h4>{{author.name}}</h4>
{{else}} {{else}}
{{#if (eq message.type 'base')}} <h4>{{alias}}</h4>
<h4>{{actor.name}}</h4> <div>{{author.name}}</div>
<div>{{author.name}}</div>
{{else}}
<h4>{{alias}}</h4>
<div>{{actor.name}} {{#if author.isGM}}(GM){{/if}}</div>
{{/if}}
{{/unless}} {{/unless}}
{{/if}} {{/if}}
</div> </div>

View file

@ -3,9 +3,9 @@
{{#each sources as |source|}} {{#each sources as |source|}}
<div class="armor-source-container"> <div class="armor-source-container">
<p class="armor-source-label">{{source.name}}</p> <p class="armor-source-label">{{source.name}}</p>
<div class="slot-bar"> <div class="slot-bar armor">
{{#times source.max}} {{#times source.max}}
<a class='armor-slot' data-value="{{add this 1}}" data-uuid="{{source.uuid}}"> <a class="slot" data-value="{{add this 1}}" data-uuid="{{source.uuid}}">
{{#if (gte ../current (add this 1))}} {{#if (gte ../current (add this 1))}}
<i class="fa-solid fa-shield" data-index="{{this}}"></i> <i class="fa-solid fa-shield" data-index="{{this}}"></i>
{{else}} {{else}}