Merge branch 'main' into release

This commit is contained in:
WBHarry 2026-02-01 01:20:57 +01:00
commit 1c70b46639
15 changed files with 153 additions and 71 deletions

View file

@ -2405,6 +2405,14 @@
"hideAttribution": {
"label": "Hide Attribution"
},
"showTokenDistance": {
"label": "Show Token Distance on Hover",
"choices": {
"always": "Always",
"encounters": "Encounters",
"never": "Never"
}
},
"expandedTitle": "Auto-expand Descriptions",
"extendCharacterDescriptions": {
"label": "Characters"

View file

@ -165,7 +165,8 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
name: game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.newDowntimeMove'),
img: 'icons/magic/life/cross-worn-green.webp',
description: '',
actions: []
actions: [],
effects: []
}
});
} else if (['armorFeatures', 'weaponFeatures'].includes(type)) {

View file

@ -18,8 +18,9 @@ export default class DhMeasuredTemplate extends foundry.canvas.placeables.Measur
static getRangeLabels(distanceValue, settings) {
let result = { distance: distanceValue, units: '' };
const sceneRangeMeasurement = canvas.scene.flags.daggerheart?.rangeMeasurement;
if (!settings.enabled) return result;
const sceneRangeMeasurement = canvas.scene.flags.daggerheart?.rangeMeasurement;
const { disable, custom } = CONFIG.DH.GENERAL.sceneRangeMeasurementSetting;
if (sceneRangeMeasurement?.setting === disable.id) {
result.distance = distanceValue;
@ -27,31 +28,9 @@ export default class DhMeasuredTemplate extends foundry.canvas.placeables.Measur
return result;
}
const melee = sceneRangeMeasurement?.setting === custom.id ? sceneRangeMeasurement.melee : settings.melee;
const veryClose =
sceneRangeMeasurement?.setting === custom.id ? sceneRangeMeasurement.veryClose : settings.veryClose;
const close = sceneRangeMeasurement?.setting === custom.id ? sceneRangeMeasurement.close : settings.close;
const far = sceneRangeMeasurement?.setting === custom.id ? sceneRangeMeasurement.far : settings.far;
if (distanceValue <= melee) {
result.distance = game.i18n.localize('DAGGERHEART.CONFIG.Range.melee.name');
return result;
}
if (distanceValue <= veryClose) {
result.distance = game.i18n.localize('DAGGERHEART.CONFIG.Range.veryClose.name');
return result;
}
if (distanceValue <= close) {
result.distance = game.i18n.localize('DAGGERHEART.CONFIG.Range.close.name');
return result;
}
if (distanceValue <= far) {
result.distance = game.i18n.localize('DAGGERHEART.CONFIG.Range.far.name');
return result;
}
if (distanceValue > far) {
result.distance = game.i18n.localize('DAGGERHEART.CONFIG.Range.veryFar.name');
}
const ranges = sceneRangeMeasurement?.setting === custom.id ? sceneRangeMeasurement : settings;
const distanceKey = ['melee', 'veryClose', 'close', 'far'].find(r => ranges[r] >= distanceValue);
result.distance = game.i18n.localize(`DAGGERHEART.CONFIG.Range.${distanceKey ?? 'veryFar'}.name`);
return result;
}
}

View file

@ -1,3 +1,5 @@
import DhMeasuredTemplate from "./measuredTemplate.mjs";
export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
/** @inheritdoc */
async _draw(options) {
@ -78,6 +80,60 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
return canvas.grid.measurePath([adjustedOriginPoint, adjustDestinationPoint]).distance;
}
_onHoverIn(event, options) {
super._onHoverIn(event, options);
// Check if the setting is enabled
const setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance).showTokenDistance;
if (setting === "never" || (setting === "encounters" && !game.combat?.started)) return;
// Check if this token isn't invisible and is actually being hovered
const isTokenValid =
this.visible &&
this.hover &&
!this.isPreview &&
!this.document.isSecret &&
!this.controlled &&
!this.animation;
if (!isTokenValid) return;
// Ensure we have a single controlled token
const originToken = canvas.tokens.controlled[0];
if (!originToken || canvas.tokens.controlled.length > 1) return;
// Determine the actual range
const ranges = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.variantRules).rangeMeasurement;
const distanceNum = originToken.distanceTo(this);
const distanceResult = DhMeasuredTemplate.getRangeLabels(distanceNum, ranges);
const distanceLabel = `${distanceResult.distance} ${distanceResult.units}`.trim();
// Create the element
const element = document.createElement('div');
element.id = 'token-hover-distance';
element.classList.add('waypoint-label', 'last');
const ruler = document.createElement('i');
ruler.classList.add('fa-solid', 'fa-ruler');
element.appendChild(ruler);
const labelEl = document.createElement('span');
labelEl.classList.add('total-measurement');
labelEl.textContent = distanceLabel;
element.appendChild(labelEl);
// Position the element and add to the DOM
const center = this.getCenterPoint();
element.style.setProperty('--transformY', 'calc(-100% - 10px)');
element.style.setProperty('--position-y', `${this.y}px`);
element.style.setProperty('--position-x', `${center.x}px`);
element.style.setProperty('--ui-scale', String(canvas.dimensions.uiScale));
document.querySelector('#token-hover-distance')?.remove();
document.querySelector('#measurement').appendChild(element);
}
_onHoverOut(...args) {
super._onHoverOut(...args);
document.querySelector('#token-hover-distance')?.remove();
}
/** Returns the point at which a line starting at origin and ending at destination intersects the edge of the bounds */
#getEdgeBoundary(bounds, originPoint, destinationPoint) {
const points = [

View file

@ -252,7 +252,8 @@ export const defaultRestOptions = {
]
}
}
}
},
effects: []
},
clearStress: {
id: 'clearStress',
@ -285,7 +286,8 @@ export const defaultRestOptions = {
]
}
}
}
},
effects: []
},
repairArmor: {
id: 'repairArmor',
@ -318,7 +320,8 @@ export const defaultRestOptions = {
]
}
}
}
},
effects: []
},
prepare: {
id: 'prepare',
@ -326,7 +329,8 @@ export const defaultRestOptions = {
icon: 'fa-solid fa-dumbbell',
img: 'icons/skills/trades/academics-merchant-scribe.webp',
description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.shortRest.prepare.description'),
actions: {}
actions: {},
effects: []
}
}),
longRest: () => ({
@ -361,7 +365,8 @@ export const defaultRestOptions = {
]
}
}
}
},
effects: []
},
clearStress: {
id: 'clearStress',
@ -394,7 +399,8 @@ export const defaultRestOptions = {
]
}
}
}
},
effects: []
},
repairArmor: {
id: 'repairArmor',
@ -427,7 +433,8 @@ export const defaultRestOptions = {
]
}
}
}
},
effects: []
},
prepare: {
id: 'prepare',
@ -435,7 +442,8 @@ export const defaultRestOptions = {
icon: 'fa-solid fa-dumbbell',
img: 'icons/skills/trades/academics-merchant-scribe.webp',
description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.prepare.description'),
actions: {}
actions: {},
effects: []
},
workOnAProject: {
id: 'workOnAProject',
@ -443,7 +451,8 @@ export const defaultRestOptions = {
icon: 'fa-solid fa-diagram-project',
img: 'icons/skills/social/thumbsup-approval-like.webp',
description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.workOnAProject.description'),
actions: {}
actions: {},
effects: []
}
})
};

View file

@ -114,9 +114,24 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
* Return Item the action is attached too.
*/
get item() {
if (!this.parent.parent && this.systemPath)
return foundry.utils.getProperty(this.parent, this.systemPath).get(this.id);
return this.parent.parent;
}
get applyEffects() {
if (this.item.systemPath) {
const itemEffectIds = this.item.effects.map(x => x._id);
const movePathSplit = this.item.systemPath.split('.');
movePathSplit.pop();
const move = foundry.utils.getProperty(this.parent, movePathSplit.join('.'));
return new Collection(itemEffectIds.map(id => [id, move.effects.find(x => x.id === id)]));
}
return this.item.effects;
}
/**
* Return the first Actor parent found.
*/
@ -125,7 +140,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
? this.item
: this.item?.parent instanceof DhpActor
? this.item.parent
: this.item?.actor;
: null;
}
static getRollType(parent) {

View file

@ -41,7 +41,8 @@ export default class DhCharacter extends BaseDataActor {
min: 0,
integer: true,
label: 'DAGGERHEART.GENERAL.hope'
})
}),
isReversed: new fields.BooleanField({ initial: false })
})
}),
traits: new fields.SchemaField({

View file

@ -73,7 +73,7 @@ export default class EffectsField extends fields.ArrayField {
});
effects.forEach(async e => {
const effect = this.item.effects.get(e._id);
const effect = (this.item.applyEffects ?? this.item.effects).get(e._id);
if (!token.actor || !effect) return;
await EffectsField.applyEffect(effect, token.actor);
});
@ -96,7 +96,7 @@ export default class EffectsField extends fields.ArrayField {
content: await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/ui/chat/effectSummary.hbs',
{
effects: this.effects.map(e => this.item.effects.get(e._id)),
effects: this.effects.map(e => (this.item.applyEffects ?? this.item.effects).get(e._id)),
targets: messageTargets
}
)
@ -123,7 +123,7 @@ export default class EffectsField extends fields.ArrayField {
// Otherwise, create a new effect on the target
const effectData = foundry.utils.mergeObject({
...effect.toObject(),
...(effect.toObject?.() ?? effect),
disabled: false,
transfer: false,
origin: effect.uuid

View file

@ -152,6 +152,7 @@ export function ActionMixin(Base) {
}
get uuid() {
if (!(this.item instanceof game.system.api.documents.DHItem)) return null;
return `${this.item.uuid}.${this.documentName}.${this.id}`;
}

View file

@ -42,6 +42,25 @@ export default class DhAppearance extends foundry.abstract.DataModel {
damage: new BooleanField(),
target: new BooleanField()
}),
showTokenDistance: new StringField({
required: true,
choices: {
always: {
value: 'always',
label: 'DAGGERHEART.SETTINGS.Appearance.FIELDS.showTokenDistance.choices.always'
},
encounters: {
value: 'encounters',
label: 'DAGGERHEART.SETTINGS.Appearance.FIELDS.showTokenDistance.choices.encounters'
},
never: {
value: 'never',
label: 'DAGGERHEART.SETTINGS.Appearance.FIELDS.showTokenDistance.choices.never'
}
},
nullable: false,
initial: 'always'
}),
hideAttribution: new BooleanField(),
showGenericStatusEffects: new BooleanField({ initial: true })
};

View file

@ -12,6 +12,20 @@ const currencyField = (initial, label, icon) =>
icon: new foundry.data.fields.StringField({ required: true, nullable: false, blank: true, initial: icon })
});
const restMoveField = () =>
new foundry.data.fields.SchemaField({
name: new foundry.data.fields.StringField({ required: true }),
icon: new foundry.data.fields.StringField({ required: true }),
img: new foundry.data.fields.FilePathField({
initial: 'icons/magic/life/cross-worn-green.webp',
categories: ['IMAGE'],
base64: false
}),
description: new foundry.data.fields.HTMLField(),
actions: new ActionsField(),
effects: new foundry.data.fields.ArrayField(new foundry.data.fields.ObjectField())
});
export default class DhHomebrew extends foundry.abstract.DataModel {
static defineSchema() {
const fields = foundry.data.fields;
@ -105,37 +119,11 @@ export default class DhHomebrew extends foundry.abstract.DataModel {
restMoves: new fields.SchemaField({
longRest: new fields.SchemaField({
nrChoices: new fields.NumberField({ required: true, integer: true, min: 1, initial: 2 }),
moves: new fields.TypedObjectField(
new fields.SchemaField({
name: new fields.StringField({ required: true }),
icon: new fields.StringField({ required: true }),
img: new fields.FilePathField({
initial: 'icons/magic/life/cross-worn-green.webp',
categories: ['IMAGE'],
base64: false
}),
description: new fields.HTMLField(),
actions: new ActionsField()
}),
{ initial: defaultRestOptions.longRest() }
)
moves: new fields.TypedObjectField(restMoveField(), { initial: defaultRestOptions.longRest() })
}),
shortRest: new fields.SchemaField({
nrChoices: new fields.NumberField({ required: true, integer: true, min: 1, initial: 2 }),
moves: new fields.TypedObjectField(
new fields.SchemaField({
name: new fields.StringField({ required: true }),
icon: new fields.StringField({ required: true }),
img: new fields.FilePathField({
initial: 'icons/magic/life/cross-worn-green.webp',
categories: ['IMAGE'],
base64: false
}),
description: new fields.HTMLField(),
actions: new ActionsField()
}),
{ initial: defaultRestOptions.shortRest() }
)
moves: new fields.TypedObjectField(restMoveField(), { initial: defaultRestOptions.shortRest() })
})
}),
domains: new fields.TypedObjectField(

View file

@ -269,7 +269,7 @@ export default class DHToken extends CONFIG.Token.documentClass {
// Hexagon symmetry
if (columns) {
const rowData = BaseToken.#getHexagonalShape(height, width, shape, false);
const rowData = DHToken.#getHexagonalShape(height, width, shape, false);
if (!rowData) return null;
// Transpose the offsets/points of the shape in row orientation

View file

@ -4,6 +4,7 @@ export default function DhTemplateEnricher(match, _options) {
const params = parseInlineParams(match[1]);
const { type, angle = CONFIG.MeasuredTemplate.defaults.angle, inline = false } = params;
const direction = Number(params.direction) || 0;
params.range = params.range?.toLowerCase();
const range =
params.range && Number.isNaN(Number(params.range))
? Object.values(CONFIG.DH.GENERAL.templateRanges).find(

View file

@ -2,7 +2,7 @@
"id": "daggerheart",
"title": "Daggerheart",
"description": "An unofficial implementation of the Daggerheart system",
"version": "1.6.1",
"version": "1.6.2",
"compatibility": {
"minimum": "13.346",
"verified": "13.351",

View file

@ -16,6 +16,10 @@
value=setting.showGenericStatusEffects
localize=true}}
{{formGroup
fields.showTokenDistance
value=setting.showTokenDistance
localize=true}}
{{formGroup
fields.hideAttribution
value=setting.hideAttribution
localize=true}}